Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions client/src/components/cos/tabs/schedule/AppOverrideRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ToggleSwitch from '../../../ToggleSwitch';
import useFieldDraft from '../../../../hooks/useFieldDraft';
import { INTERVAL_LABELS, setMetadataOverride } from './scheduleConstants';

const AppOverrideRow = memo(function AppOverrideRow({ app, taskType, globalIntervalType, globalTaskMetadata, managedAgentOptions, fileIssuesCapable, defaultFileIssues, inheritedProviderText, providers, override, onUpdate }) {
const AppOverrideRow = memo(function AppOverrideRow({ app, taskType, globalIntervalType, globalTaskMetadata, managedAgentOptions, fileIssuesCapable, defaultFileIssues, doWorkRequiresWorktree, inheritedProviderText, providers, override, onUpdate }) {
const [updating, setUpdating] = useState(false);
const [cronEditing, setCronEditing] = useState(false);
const isEnabled = override?.enabled === true;
Expand Down Expand Up @@ -61,6 +61,8 @@ const AppOverrideRow = memo(function AppOverrideRow({ app, taskType, globalInter
: null;
if (field === 'fileIssues' && nextFileIssues === true && taskMetadata) {
taskMetadata = { ...taskMetadata, useWorktree: false, openPR: false, simplify: false };
} else if (field === 'fileIssues' && nextFileIssues === false && doWorkRequiresWorktree && taskMetadata) {
taskMetadata = { ...taskMetadata, useWorktree: true };
}
await onUpdate(app.id, taskType, { taskMetadata }).catch(() => {});
setUpdating(false);
Expand Down Expand Up @@ -175,7 +177,9 @@ const AppOverrideRow = memo(function AppOverrideRow({ app, taskType, globalInter
const effective = override?.taskMetadata?.[field] ?? globalTaskMetadata?.[field] ?? false;
const hasOverride = override?.taskMetadata?.[field] !== undefined;
const fileIssuesOn = (override?.taskMetadata?.fileIssues ?? globalTaskMetadata?.fileIssues ?? defaultFileIssues) === true;
const managed = managedAgentOptions?.includes(field) || (fileIssuesCapable && fileIssuesOn && ['useWorktree', 'openPR', 'simplify'].includes(field));
const managed = managedAgentOptions?.includes(field)
|| (fileIssuesCapable && fileIssuesOn && ['useWorktree', 'openPR', 'simplify'].includes(field))
|| (doWorkRequiresWorktree && !fileIssuesOn && field === 'useWorktree');
const titleText = managed
? `${label}: managed internally by ${taskType}`
: `${label}: ${effective ? 'on' : 'off'}${hasOverride ? ' (app override)' : ' (inherited)'}`;
Expand Down
18 changes: 18 additions & 0 deletions client/src/components/cos/tabs/schedule/AppOverrideRow.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function renderRow({
taskType = 'feature-ideas',
fileIssuesCapable,
defaultFileIssues,
doWorkRequiresWorktree,
inheritedProviderText,
providers,
} = {}) {
Expand All @@ -24,6 +25,7 @@ function renderRow({
managedAgentOptions={[]}
fileIssuesCapable={fileIssuesCapable}
defaultFileIssues={defaultFileIssues}
doWorkRequiresWorktree={doWorkRequiresWorktree}
inheritedProviderText={inheritedProviderText}
providers={providers}
override={override}
Expand Down Expand Up @@ -188,6 +190,22 @@ describe('AppOverrideRow — file issues only', () => {
taskMetadata: { fileIssues: true, useWorktree: false, openPR: false, simplify: false },
});
});

it('restores required worktree isolation in a per-app do-work override', async () => {
const onUpdate = renderRow({
taskType: 'module-hygiene',
fileIssuesCapable: true,
defaultFileIssues: true,
doWorkRequiresWorktree: true,
globalTaskMetadata: { fileIssues: true, useWorktree: false, openPR: false },
});
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /File issues only/i }));
});
expect(onUpdate).toHaveBeenCalledWith('app-1', 'module-hygiene', {
taskMetadata: { fileIssues: false, useWorktree: true },
});
});
});

describe('AppOverrideRow — branch-reconcile batch size', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,11 @@ export default function GlobalConfigControls({ taskType, config, onUpdate, onTri
onClick={async () => {
setUpdating(true);
await onUpdate(taskType, {
taskMetadata: toggleFileIssuesMetadata(config.taskMetadata, !fileIssuesEffective(config))
taskMetadata: toggleFileIssuesMetadata(
config.taskMetadata,
!fileIssuesEffective(config),
config.doWorkRequiresWorktree,
)
});
setUpdating(false);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,16 @@ describe('GlobalConfigControls — file issues only', () => {
taskMetadata: { useWorktree: false, openPR: false, simplify: false, fileIssues: true },
});
});

it('restores required worktree isolation when module-hygiene switches to do-work mode', () => {
const onUpdate = renderControls({
taskType: 'module-hygiene',
taskMetadata: { useWorktree: false, openPR: false, simplify: false, fileIssues: true },
config: { fileIssuesCapable: true, defaultFileIssues: true, doWorkRequiresWorktree: true },
});
fireEvent.click(screen.getByRole('button', { name: /File issues only/i }));
expect(onUpdate).toHaveBeenCalledWith('module-hygiene', {
taskMetadata: { useWorktree: true, openPR: false, simplify: false, fileIssues: false },
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default function PerAppOverrideList({ taskType, config, apps, providers,
managedAgentOptions={config.managedAgentOptions}
fileIssuesCapable={config.fileIssuesCapable}
defaultFileIssues={config.defaultFileIssues}
doWorkRequiresWorktree={config.doWorkRequiresWorktree}
inheritedProviderText={inheritedProviderText}
providers={providers}
override={appOverrides[app.id]}
Expand Down
10 changes: 8 additions & 2 deletions client/src/components/cos/tabs/schedule/scheduleConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,20 +199,26 @@ export const FILE_ISSUES_MANAGED_FIELDS = ['useWorktree', 'openPR', 'simplify'];

export function managedAgentOptionsFor(config, overrideMetadata) {
const managed = [...(config?.managedAgentOptions || [])];
if (config?.fileIssuesCapable && fileIssuesEffective(config, overrideMetadata)) {
const fileIssues = fileIssuesEffective(config, overrideMetadata);
if (config?.fileIssuesCapable && fileIssues) {
for (const field of FILE_ISSUES_MANAGED_FIELDS) {
if (!managed.includes(field)) managed.push(field);
}
}
if (config?.doWorkRequiresWorktree && !fileIssues && !managed.includes('useWorktree')) {
managed.push('useWorktree');
}
return managed;
}

export function toggleFileIssuesMetadata(metadata, next) {
export function toggleFileIssuesMetadata(metadata, next, doWorkRequiresWorktree = false) {
const taskMetadata = { ...(metadata || {}), fileIssues: next };
if (next) {
taskMetadata.useWorktree = false;
taskMetadata.openPR = false;
taskMetadata.simplify = false;
} else if (doWorkRequiresWorktree) {
taskMetadata.useWorktree = true;
}
return taskMetadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,31 @@ describe('managedAgentOptionsFor', () => {
it('leaves non-audit tasks alone', () => {
expect(managedAgentOptionsFor({ managedAgentOptions: ['useWorktree'] })).toEqual(['useWorktree']);
});

it('locks the worktree on for an isolation-required audit in do-work mode', () => {
expect(managedAgentOptionsFor({
fileIssuesCapable: true,
defaultFileIssues: true,
doWorkRequiresWorktree: true,
}, { fileIssues: false })).toEqual(['useWorktree']);
});
});

describe('toggleFileIssuesMetadata', () => {
it('forces the no-code posture on and leaves it when turning off', () => {
it('forces the no-code posture on and otherwise leaves agent options alone', () => {
expect(toggleFileIssuesMetadata({ useWorktree: true, openPR: true, simplify: true }, true))
.toEqual({ useWorktree: false, openPR: false, simplify: false, fileIssues: true });
expect(toggleFileIssuesMetadata({ fileIssues: true, useWorktree: false }, false))
.toEqual({ fileIssues: false, useWorktree: false });
});

it('restores required worktree isolation when do-work mode is selected', () => {
expect(toggleFileIssuesMetadata(
{ fileIssues: true, useWorktree: false, openPR: false },
false,
true,
)).toEqual({ fileIssues: false, useWorktree: true, openPR: false });
});
});

describe('toggleMetadataField', () => {
Expand Down
35 changes: 35 additions & 0 deletions data.reference/prompts/skills/module-hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Module Hygiene Audit Skill Template

## Routing
**Use when**: The task is explicitly a module-hygiene audit or remediation run.
**Don't use when**: The task only removes dead code, fixes an unrelated defect, or asks for a broad rewrite without evidence.

## Task-Specific Guidelines

Improve structural maintainability without treating code size as a defect.

### 1. Thresholds nominate; evidence decides
- Use complexity, function length, nesting, file size, churn, and fan-in to select candidates.
- Keep a finding only after tracing responsibilities, callers, tests, and history to a concrete change cost.
- Reject primarily declarative, generated, compatibility, mirror, and semantic-adapter false positives.

### 2. Prove reuse discovery
- Search the repository's own catalogs, domain maps, public exports, naming variants, and importers.
- Record why an existing module should be extended or why a new public seam is necessary.
- Prefer consolidation that deletes a duplicate implementation over a wrapper that preserves both.

### 3. Match discovery to the surface
- Curate and parity-check a catalog only for a genuinely reusable/public surface.
- Prefer lightweight ownership and placement guidance for broad implementation directories.
- Never require exhaustive catalogs or barrels merely because a directory is large.

### 4. De-duplicate across history
- Search open and closed tracker work plus merged changes by file, symbol, and behavior.
- Link or reuse sibling simplification or code-quality work instead of filing it again under a new label.

### 5. Mode
In file-issues mode, change no source and file only decision-complete findings. In implementation mode, make one behavior-preserving improvement in the isolated worktree and verify it at the highest practical public boundary.

## Successful Outcome

The run names its bounded slice and reuse searches, reports what it rejected, and produces zero to three high-confidence findings normally. A well-supported no-finding result is successful.
2 changes: 1 addition & 1 deletion server/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The barrel `server/lib/index.js` is a machine-checkable enumeration of every pub
| `agentValidation.js` | Social-bot agent schemas (personality, Moltbook/Moltworld accounts, automation schedules, agent tools + Moltworld payloads) and CoS Feature Agent definitions. |
| `quotaBurnConfig.js` | Quota-burn plan shape: the provider families, the burn-job type alphabet + catalog the config page renders its form from, `QUOTA_BURN_BOUNDS` (the one bounds table the normalizer clamps to, the Zod schemas reject against, and the catalog descriptors publish as min/max), and total normalization (`normalizeQuotaBurnConfig`). Owns the dispatch-cap sentinel too (`QUOTA_BURN_UNLIMITED_DISPATCHES` / `isUnlimitedDispatchCap`) — -1 means the window is not counted, and is the default. Also owns the queued burn task's description shape (`burnTaskDescription` / `quotaBurnFamilyOfDescription`), shared with migration 225, and the `run once` vocabulary (`quotaBurnJobKey` / `jobIsSpent`, plus the two family predicates `familyIsConfigured` and `familyHasRunnableJobs`). Pure — no storage, no provider I/O. |
| `quotaBurnPresets.js` | `QUOTA_BURN_PROMPT_PRESETS` — ready-made single-focus audit prompts for `agent-prompt` burn jobs (UX, a11y, mobile, failure paths, perf, test gaps, dead code, data safety, docs, security), each filing GitHub issues and changing no code. Templates: picking one COPIES its prompt into the job, so editing them never rewrites a configured job. `findQuotaBurnPreset(id)`. |
| `auditCatalog.js` | Shared catalog of scheduled AUDIT task types (`AUDIT_DEFINITIONS` / `AUDIT_TASK_TYPES`) that can either implement a fix or file tracker issues. `isFileIssuesMode`, `getAuditFilingPreset`, `modeContractFor`, `applyAuditModeWrapper`. Each quota-burn audit preset maps to a scheduled type here (guarded by `auditCatalog.test.js`). Pure — no I/O. |
| `auditCatalog.js` | Shared catalog of scheduled AUDIT task types (`AUDIT_DEFINITIONS` / `AUDIT_TASK_TYPES`) that can either implement a fix or file tracker issues. `isFileIssuesMode`, `auditDoWorkRequiresWorktree`, `getAuditFilingPreset`, `modeContractFor`, `applyAuditModeWrapper`. Each quota-burn audit preset maps to a scheduled type here (guarded by `auditCatalog.test.js`). Pure — no I/O. |
| `quotaBurnValidation.js` | Zod schemas for the Quota Burn routes (partial config PUT, manual-run body, `run once` re-arm body). |
| `quotaReset.js` | `parseHumanReset` turns a provider CLI's human reset string into ISO 8601 (call it from the adapter); `normalizeResetAt`/`hoursUntilReset` compute time remaining without treating unknown values as imminent resets; `parseObservedReset` pulls the reset a provider stated in its own refusal text, and `isObservedBlockActive` is the shared "is that refusal still holding?" predicate every observed-refusal ledger uses. |
| `quotaWindows.js` | Classifies a quota window by PERIOD (`windowPeriodHours`); `classifyWindows` splits one card's windows into the weekly allowance that expires unused and the 5-hour one that refuses first, in one pass. `windowLabelOf` names a window. Pure. |
Expand Down
25 changes: 25 additions & 0 deletions server/lib/auditCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,20 @@ export const AUDIT_DEFINITIONS = Object.freeze({
noun: 'simplify finding(s)',
}),
},
'module-hygiene': {
quotaBurnId: null,
label: 'Module hygiene',
description: 'Module-hygiene audit — configurable: file issues (default) or implement one isolated refactor',
defaultFileIssues: true,
doWorkRequiresWorktree: true,
filing: filing({
slugPrefix: 'module-hygiene-',
label: 'module-hygiene-audit',
issueLabel: 'code-quality',
labelDescription: 'Proposed from a module-hygiene audit',
noun: 'module-hygiene finding(s)',
}),
},
'api-contract': {
quotaBurnId: 'api-contract-audit',
label: 'API & route contracts',
Expand Down Expand Up @@ -329,6 +343,17 @@ export function defaultFileIssuesFor(taskType) {
return AUDIT_DEFINITIONS[taskType]?.defaultFileIssues === true;
}

/**
* Whether do-work mode for this audit must use an isolated managed worktree.
* The flag is catalog-owned so dispatch and schedule UI cannot drift.
*
* @param {string} taskType - Task type identifier
* @returns {boolean} True when live-checkout remediation is forbidden
*/
export function auditDoWorkRequiresWorktree(taskType) {
return AUDIT_DEFINITIONS[taskType]?.doWorkRequiresWorktree === true;
}

/**
* Effective file-issues mode for a dispatch. An explicit `fileIssues` boolean
* on the merged task metadata wins; otherwise the catalog default applies.
Expand Down
9 changes: 9 additions & 0 deletions server/lib/auditCatalog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DO_WORK_MODE_CONTRACT,
isAuditTaskType,
defaultFileIssuesFor,
auditDoWorkRequiresWorktree,
isFileIssuesMode,
getAuditFilingPreset,
modeContractFor,
Expand Down Expand Up @@ -36,10 +37,17 @@ describe('AUDIT_DEFINITIONS', () => {
expect(defaultFileIssuesFor('ux')).toBe(true);
expect(defaultFileIssuesFor('data-safety')).toBe(true);
expect(defaultFileIssuesFor('simplify')).toBe(true);
expect(defaultFileIssuesFor('module-hygiene')).toBe(true);
expect(defaultFileIssuesFor('security')).toBe(false);
expect(defaultFileIssuesFor('accessibility')).toBe(false);
expect(defaultFileIssuesFor('unknown')).toBe(false);
});

it('declares isolation as a catalog capability only for audits that require it', () => {
expect(auditDoWorkRequiresWorktree('module-hygiene')).toBe(true);
expect(auditDoWorkRequiresWorktree('simplify')).toBe(false);
expect(auditDoWorkRequiresWorktree('unknown')).toBe(false);
});
});

describe('isFileIssuesMode', () => {
Expand Down Expand Up @@ -99,6 +107,7 @@ describe('getAuditFilingPreset', () => {
it('returns the preset for an audit type and null otherwise', () => {
expect(getAuditFilingPreset('data-safety').slugPrefix).toBe('data-safety-');
expect(getAuditFilingPreset('simplify').issueLabel).toBe('code-quality');
expect(getAuditFilingPreset('module-hygiene').slugPrefix).toBe('module-hygiene-');
expect(getAuditFilingPreset('claim-issue')).toBeNull();
});

Expand Down
Loading