mandrel 2.64.0 → 2.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/acceptance-critic.md +8 -7
- package/.agents/agents/auditor.md +20 -20
- package/.agents/agents/plan-critic.md +8 -7
- package/.agents/agents/story-worker.md +7 -7
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +1 -9
- package/.agents/docs/configuration.md +8 -7
- package/.agents/docs/execution-reference.md +27 -5
- package/.agents/instructions.md +10 -12
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +3 -2
- package/.agents/rules/git-conventions-reference.md +12 -3
- package/.agents/rules/git-conventions.md +9 -7
- package/.agents/rules/testing-standards.md +8 -7
- package/.agents/runtime-deps.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -13
- package/.agents/schemas/audit-rules.schema.json +1 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +5 -0
- package/.agents/scripts/bootstrap.js +102 -91
- package/.agents/scripts/check-context-budget.js +1 -1
- package/.agents/scripts/lib/ITicketingProvider.js +1 -3
- package/.agents/scripts/lib/audit-suite/findings.js +1 -17
- package/.agents/scripts/lib/audit-suite/frontmatter.js +0 -28
- package/.agents/scripts/lib/audit-suite/index.js +0 -6
- package/.agents/scripts/lib/audit-suite/selector.js +0 -31
- package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
- package/.agents/scripts/lib/bootstrap/agents-md-fold.js +156 -0
- package/.agents/scripts/lib/bootstrap/commit-push.js +1 -1
- package/.agents/scripts/lib/bootstrap/manifest.js +2 -2
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +91 -107
- package/.agents/scripts/lib/cli/standard-args.js +60 -76
- package/.agents/scripts/lib/cli-args.js +26 -0
- package/.agents/scripts/lib/config/gates/shared.js +3 -3
- package/.agents/scripts/lib/config/review-chain-default.js +13 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +11 -13
- package/.agents/scripts/lib/doc-tiers.js +25 -6
- package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
- package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +0 -72
- package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
- package/.agents/scripts/lib/observability/signal-validator.js +17 -5
- package/.agents/scripts/lib/orchestration/code-review.js +33 -6
- package/.agents/scripts/lib/orchestration/epic-rollup.js +29 -12
- package/.agents/scripts/lib/orchestration/merge-block-class.js +20 -4
- package/.agents/scripts/lib/orchestration/merge-poll.js +41 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
- package/.agents/scripts/lib/orchestration/required-checks.js +147 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +203 -0
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +29 -4
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -2
- package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
- package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +1 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -12
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +370 -268
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +112 -82
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +393 -313
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +12 -87
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +3 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
- package/.agents/scripts/lib/signals/detectors/common.js +63 -51
- package/.agents/scripts/lib/templates/decomposer-prompts.js +5 -24
- package/.agents/scripts/lib/transpile.js +28 -3
- package/.agents/scripts/providers/github/issues.js +14 -23
- package/.agents/scripts/single-story-close.js +10 -2
- package/.agents/scripts/single-story-confirm-merge.js +267 -238
- package/.agents/scripts/sync-claude-agents.js +1 -1
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
- package/.agents/workflows/audit-architecture.md +5 -4
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-performance.md +10 -10
- package/.agents/workflows/audit-quality.md +42 -7
- package/.agents/workflows/helpers/acceptance-self-eval.md +9 -9
- package/.agents/workflows/helpers/audit-lens-core.md +30 -57
- package/.agents/workflows/helpers/code-review.md +15 -38
- package/.agents/workflows/helpers/deliver-digest.md +2 -2
- package/.agents/workflows/helpers/deliver-reference.md +7 -3
- package/.agents/workflows/helpers/deliver-story.md +9 -1
- package/.agents/workflows/helpers/parallel-tooling.md +16 -18
- package/.agents/workflows/helpers/plan-reference.md +9 -8
- package/.agents/workflows/mandrel-deliver.md +3 -2
- package/.agents/workflows/mandrel-plan.md +11 -7
- package/.agents/workflows/mandrel-update.md +5 -3
- package/docs/CHANGELOG.md +57 -0
- package/lib/cli/claude-code-version.js +73 -0
- package/lib/cli/doctor.js +2 -2
- package/lib/cli/guarded-sync.js +87 -0
- package/lib/cli/registry.js +9 -0
- package/lib/cli/sync-agents.js +9 -92
- package/lib/cli/sync-commands.js +9 -101
- package/lib/cli/uninstall.js +37 -9
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js +38 -0
- package/package.json +3 -2
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +0 -99
- package/.agents/scripts/lib/audit-suite/runner.js +0 -205
- package/.agents/scripts/lib/audit-suite/substitutions.js +0 -96
- package/.agents/scripts/lib/audit-suite/workflow-loader.js +0 -37
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +0 -234
|
@@ -11,6 +11,12 @@ import path from 'node:path';
|
|
|
11
11
|
import { pathToFileURL } from 'node:url';
|
|
12
12
|
import { isCommandExcluded } from '../command-header.js';
|
|
13
13
|
import { detectPackageManager as detectPm } from '../detect-package-manager.js';
|
|
14
|
+
import {
|
|
15
|
+
SYSTEM_PROMPT_BLOCK,
|
|
16
|
+
SYSTEM_PROMPT_ENTRY_DOC,
|
|
17
|
+
SYSTEM_PROMPT_IMPORT,
|
|
18
|
+
wireEntryDoc,
|
|
19
|
+
} from './agents-md-fold.js';
|
|
14
20
|
import { LEDGER_RELATIVE_PATH } from './install-ledger.js';
|
|
15
21
|
import { ensureIssueForms } from './issue-forms-template.js';
|
|
16
22
|
import { PHASE_GROUPS } from './manifest.js';
|
|
@@ -23,17 +29,10 @@ export const SYNC_AGENTS_COMMAND = 'node .agents/scripts/sync-claude-agents.js';
|
|
|
23
29
|
|
|
24
30
|
export const BOOTSTRAP_COMMAND = 'node .agents/scripts/bootstrap.js';
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
export const SYSTEM_PROMPT_IMPORT = '@.agents/instructions.md';
|
|
32
|
+
export { SYSTEM_PROMPT_BLOCK, SYSTEM_PROMPT_IMPORT };
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
${SYSTEM_PROMPT_IMPORT}
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
export const SYSTEM_PROMPT_CLAUDE_MD = `# Agent Protocols
|
|
35
|
-
|
|
36
|
-
${SYSTEM_PROMPT_BLOCK}`;
|
|
34
|
+
/** Install template for a fresh AGENTS.md (also the legacy CLAUDE.md one). */
|
|
35
|
+
export const SYSTEM_PROMPT_AGENTS_MD = SYSTEM_PROMPT_ENTRY_DOC;
|
|
37
36
|
|
|
38
37
|
export const GITIGNORE_BLOCKS = Object.freeze({
|
|
39
38
|
commands: {
|
|
@@ -81,6 +80,10 @@ function writeJson(p, obj, fsImpl = fs) {
|
|
|
81
80
|
fsImpl.writeFileSync(p, `${JSON.stringify(obj, null, 2)}\n`, 'utf8');
|
|
82
81
|
}
|
|
83
82
|
|
|
83
|
+
function agentRootOf(ctx) {
|
|
84
|
+
return ctx.agentRoot ?? path.join(ctx.projectRoot, '.agents');
|
|
85
|
+
}
|
|
86
|
+
|
|
84
87
|
/**
|
|
85
88
|
* Node floor SSOT (`node:sqlite` stabilised at 22.22.1); import it, never
|
|
86
89
|
* duplicate. Matches `package.json` `engines.node` (`>=22.22.1 <25`).
|
|
@@ -127,9 +130,36 @@ export function detectPackageManager(projectRoot, fsImpl = fs) {
|
|
|
127
130
|
return detectPm(projectRoot, (p) => fsImpl.existsSync(p)) ?? 'npm';
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
/** @returns {'added'|'already-present'} */
|
|
134
|
+
function ensureScript(scripts, key, command) {
|
|
135
|
+
if (scripts[key]) return 'already-present';
|
|
136
|
+
scripts[key] = command;
|
|
137
|
+
return 'added';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Append each projection independently so a partial prepare gains the other. */
|
|
141
|
+
function ensurePrepareScript(scripts) {
|
|
142
|
+
const prepare = scripts.prepare;
|
|
143
|
+
if (!prepare) {
|
|
144
|
+
scripts.prepare = `${SYNC_COMMAND} && ${SYNC_AGENTS_COMMAND}`;
|
|
145
|
+
return 'added';
|
|
146
|
+
}
|
|
147
|
+
let next = prepare;
|
|
148
|
+
if (!next.includes('sync-claude-commands.js')) {
|
|
149
|
+
next = `${next} && ${SYNC_COMMAND}`;
|
|
150
|
+
}
|
|
151
|
+
if (!next.includes('sync-claude-agents.js')) {
|
|
152
|
+
next = `${next} && ${SYNC_AGENTS_COMMAND}`;
|
|
153
|
+
}
|
|
154
|
+
if (next === prepare) return 'already-present';
|
|
155
|
+
scripts.prepare = next;
|
|
156
|
+
return 'appended';
|
|
157
|
+
}
|
|
158
|
+
|
|
130
159
|
/**
|
|
131
160
|
* Ensure `package.json` carries the sync/prepare/bootstrap scripts. Never
|
|
132
161
|
* touches `dependencies` — framework deps arrive transitively via `mandrel`.
|
|
162
|
+
* An operator-defined `bootstrap` script always wins.
|
|
133
163
|
*
|
|
134
164
|
* @param {object} ctx
|
|
135
165
|
* @param {typeof fs} [ctx.fsImpl]
|
|
@@ -137,62 +167,32 @@ export function detectPackageManager(projectRoot, fsImpl = fs) {
|
|
|
137
167
|
export function ensurePackageJson(ctx) {
|
|
138
168
|
const { fsImpl = fs } = ctx;
|
|
139
169
|
const pkgPath = path.join(ctx.projectRoot, 'package.json');
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
scriptsBootstrap: 'already-present',
|
|
170
|
+
const existing = readJsonIfExists(pkgPath, fsImpl);
|
|
171
|
+
const pkg = existing || {
|
|
172
|
+
name: path.basename(path.resolve(ctx.projectRoot)),
|
|
173
|
+
version: '0.0.0',
|
|
174
|
+
private: true,
|
|
175
|
+
type: 'module',
|
|
147
176
|
};
|
|
148
|
-
let pkg = readJsonIfExists(pkgPath, fsImpl);
|
|
149
|
-
if (!pkg) {
|
|
150
|
-
pkg = {
|
|
151
|
-
name: projectName,
|
|
152
|
-
version: '0.0.0',
|
|
153
|
-
private: true,
|
|
154
|
-
type: 'module',
|
|
155
|
-
};
|
|
156
|
-
outcomes.created = true;
|
|
157
|
-
}
|
|
158
177
|
pkg.scripts = pkg.scripts ?? {};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (!next.includes('sync-claude-agents.js')) {
|
|
178
|
-
next = `${next} && ${SYNC_AGENTS_COMMAND}`;
|
|
179
|
-
}
|
|
180
|
-
if (next !== prepare) {
|
|
181
|
-
pkg.scripts.prepare = next;
|
|
182
|
-
outcomes.scriptsPrepare = 'appended';
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// An operator-defined `bootstrap` script always wins.
|
|
186
|
-
if (!pkg.scripts.bootstrap) {
|
|
187
|
-
pkg.scripts.bootstrap = BOOTSTRAP_COMMAND;
|
|
188
|
-
outcomes.scriptsBootstrap = 'added';
|
|
189
|
-
}
|
|
190
|
-
const mutated =
|
|
191
|
-
outcomes.created ||
|
|
192
|
-
outcomes.scriptsSyncCommands === 'added' ||
|
|
193
|
-
outcomes.scriptsSyncAgents === 'added' ||
|
|
194
|
-
outcomes.scriptsPrepare !== 'already-present' ||
|
|
195
|
-
outcomes.scriptsBootstrap === 'added';
|
|
178
|
+
const outcomes = {
|
|
179
|
+
created: !existing,
|
|
180
|
+
scriptsSyncCommands: ensureScript(
|
|
181
|
+
pkg.scripts,
|
|
182
|
+
'sync:commands',
|
|
183
|
+
SYNC_COMMAND,
|
|
184
|
+
),
|
|
185
|
+
scriptsSyncAgents: ensureScript(
|
|
186
|
+
pkg.scripts,
|
|
187
|
+
'sync:agents',
|
|
188
|
+
SYNC_AGENTS_COMMAND,
|
|
189
|
+
),
|
|
190
|
+
scriptsPrepare: ensurePrepareScript(pkg.scripts),
|
|
191
|
+
scriptsBootstrap: ensureScript(pkg.scripts, 'bootstrap', BOOTSTRAP_COMMAND),
|
|
192
|
+
};
|
|
193
|
+
const mutated = Object.values(outcomes).some(
|
|
194
|
+
(v) => v === true || v === 'added' || v === 'appended',
|
|
195
|
+
);
|
|
196
196
|
if (mutated) writeJson(pkgPath, pkg, fsImpl);
|
|
197
197
|
return { ...outcomes, path: pkgPath, mutated };
|
|
198
198
|
}
|
|
@@ -247,10 +247,7 @@ export function ensureAgentrc(ctx) {
|
|
|
247
247
|
if (fsImpl.existsSync(target)) {
|
|
248
248
|
return { action: 'already-present', path: target };
|
|
249
249
|
}
|
|
250
|
-
const starter = path.join(
|
|
251
|
-
ctx.agentRoot ?? path.join(ctx.projectRoot, '.agents'),
|
|
252
|
-
'starter-agentrc.json',
|
|
253
|
-
);
|
|
250
|
+
const starter = path.join(agentRootOf(ctx), 'starter-agentrc.json');
|
|
254
251
|
if (!fsImpl.existsSync(starter)) {
|
|
255
252
|
return { action: 'missing-starter', path: target };
|
|
256
253
|
}
|
|
@@ -270,16 +267,28 @@ export function ensureAgentrc(ctx) {
|
|
|
270
267
|
return { action: 'seeded', path: target, source: 'starter' };
|
|
271
268
|
}
|
|
272
269
|
|
|
270
|
+
async function loadAgentrcValidator(schemaModule) {
|
|
271
|
+
// pathToFileURL handles Windows drive letters and percent-encoding.
|
|
272
|
+
const mod = await import(pathToFileURL(schemaModule).href);
|
|
273
|
+
return mod.getAgentrcValidator();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function agentrcVerdict(validate, data) {
|
|
277
|
+
if (!data) return { ok: false, errors: ['.agentrc.json missing'] };
|
|
278
|
+
const ok = validate(data);
|
|
279
|
+
return { ok: !!ok, errors: ok ? [] : (validate.errors ?? []) };
|
|
280
|
+
}
|
|
281
|
+
|
|
273
282
|
/**
|
|
274
283
|
* Validate `.agentrc.json` against the AJV schema; the caller decides whether to abort.
|
|
275
284
|
*
|
|
276
285
|
* @param {object} ctx
|
|
277
286
|
* @param {typeof fs} [ctx.fsImpl]
|
|
278
287
|
*/
|
|
279
|
-
|
|
288
|
+
async function validateAgentrc(ctx) {
|
|
280
289
|
const { fsImpl = fs } = ctx;
|
|
281
290
|
const schemaModule = path.join(
|
|
282
|
-
|
|
291
|
+
agentRootOf(ctx),
|
|
283
292
|
'scripts',
|
|
284
293
|
'lib',
|
|
285
294
|
'config-settings-schema.js',
|
|
@@ -287,16 +296,12 @@ export async function validateAgentrc(ctx) {
|
|
|
287
296
|
if (!fsImpl.existsSync(schemaModule)) {
|
|
288
297
|
return { ok: false, errors: ['config-settings-schema.js not found'] };
|
|
289
298
|
}
|
|
290
|
-
|
|
291
|
-
const mod = await import(pathToFileURL(schemaModule).href);
|
|
292
|
-
const validate = mod.getAgentrcValidator();
|
|
299
|
+
const validate = await loadAgentrcValidator(schemaModule);
|
|
293
300
|
const data = readJsonIfExists(
|
|
294
301
|
path.join(ctx.projectRoot, '.agentrc.json'),
|
|
295
302
|
fsImpl,
|
|
296
303
|
);
|
|
297
|
-
|
|
298
|
-
const ok = validate(data);
|
|
299
|
-
return { ok: !!ok, errors: ok ? [] : (validate.errors ?? []) };
|
|
304
|
+
return agentrcVerdict(validate, data);
|
|
300
305
|
}
|
|
301
306
|
|
|
302
307
|
/**
|
|
@@ -348,12 +353,9 @@ function ensureIssueFormsPhase(ctx) {
|
|
|
348
353
|
* @param {object} ctx
|
|
349
354
|
* @param {typeof defaultSpawnSync} [ctx.spawnImpl]
|
|
350
355
|
*/
|
|
351
|
-
|
|
356
|
+
function runSyncCommands(ctx) {
|
|
352
357
|
const { spawnImpl = defaultSpawnSync } = ctx;
|
|
353
|
-
const scriptsDir = path.join(
|
|
354
|
-
ctx.agentRoot ?? path.join(ctx.projectRoot, '.agents'),
|
|
355
|
-
'scripts',
|
|
356
|
-
);
|
|
358
|
+
const scriptsDir = path.join(agentRootOf(ctx), 'scripts');
|
|
357
359
|
const projections = [
|
|
358
360
|
{ label: 'sync-claude-commands.js', script: 'sync-claude-commands.js' },
|
|
359
361
|
{ label: 'sync-claude-agents.js', script: 'sync-claude-agents.js' },
|
|
@@ -391,10 +393,7 @@ export function runSyncCommands(ctx) {
|
|
|
391
393
|
*/
|
|
392
394
|
export function checkParity(ctx) {
|
|
393
395
|
const { fsImpl = fs } = ctx;
|
|
394
|
-
const workflowsDir = path.join(
|
|
395
|
-
ctx.agentRoot ?? path.join(ctx.projectRoot, '.agents'),
|
|
396
|
-
'workflows',
|
|
397
|
-
);
|
|
396
|
+
const workflowsDir = path.join(agentRootOf(ctx), 'workflows');
|
|
398
397
|
const commandsDir = path.join(ctx.projectRoot, '.claude', 'commands');
|
|
399
398
|
const list = (dir) =>
|
|
400
399
|
fsImpl.existsSync(dir)
|
|
@@ -420,30 +419,15 @@ export function checkParity(ctx) {
|
|
|
420
419
|
}
|
|
421
420
|
|
|
422
421
|
/**
|
|
423
|
-
* Ensure `
|
|
424
|
-
* never loads on cold start.
|
|
422
|
+
* Ensure `AGENTS.md` imports the system prompt — without it the framework
|
|
423
|
+
* never loads on cold start. A pre-existing `CLAUDE.md` is folded in first
|
|
424
|
+
* (the host would otherwise read it instead). Creates, appends, or no-ops.
|
|
425
425
|
*
|
|
426
426
|
* @param {object} ctx
|
|
427
427
|
* @param {typeof fs} [ctx.fsImpl]
|
|
428
428
|
*/
|
|
429
429
|
export function ensureSystemPromptWiring(ctx) {
|
|
430
|
-
|
|
431
|
-
const target = path.join(ctx.projectRoot, 'CLAUDE.md');
|
|
432
|
-
if (!fsImpl.existsSync(target)) {
|
|
433
|
-
fsImpl.writeFileSync(target, SYSTEM_PROMPT_CLAUDE_MD, 'utf8');
|
|
434
|
-
return { action: 'created', path: target };
|
|
435
|
-
}
|
|
436
|
-
const existing = fsImpl.readFileSync(target, 'utf8');
|
|
437
|
-
if (existing.includes(SYSTEM_PROMPT_IMPORT)) {
|
|
438
|
-
return { action: 'already-present', path: target };
|
|
439
|
-
}
|
|
440
|
-
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
441
|
-
fsImpl.writeFileSync(
|
|
442
|
-
target,
|
|
443
|
-
`${existing}${separator}\n${SYSTEM_PROMPT_BLOCK}`,
|
|
444
|
-
'utf8',
|
|
445
|
-
);
|
|
446
|
-
return { action: 'appended', path: target };
|
|
430
|
+
return wireEntryDoc(ctx.projectRoot, ctx.fsImpl);
|
|
447
431
|
}
|
|
448
432
|
|
|
449
433
|
/**
|
|
@@ -453,13 +437,13 @@ export function ensureSystemPromptWiring(ctx) {
|
|
|
453
437
|
* @param {typeof fs} [ctx.fsImpl]
|
|
454
438
|
* @param {typeof defaultSpawnSync} [ctx.spawnImpl]
|
|
455
439
|
*/
|
|
456
|
-
|
|
440
|
+
function checkWindowsGitPerf(ctx) {
|
|
457
441
|
const { fsImpl = fs, spawnImpl = defaultSpawnSync } = ctx;
|
|
458
|
-
if (os.platform() !== 'win32') {
|
|
442
|
+
if ((ctx.platform ?? os.platform()) !== 'win32') {
|
|
459
443
|
return { platform: process.platform, skipped: true };
|
|
460
444
|
}
|
|
461
445
|
const script = path.join(
|
|
462
|
-
|
|
446
|
+
agentRootOf(ctx),
|
|
463
447
|
'scripts',
|
|
464
448
|
'check-windows-git-perf.js',
|
|
465
449
|
);
|
|
@@ -24,13 +24,26 @@ const SUPPORTED_FLAGS = Object.freeze({
|
|
|
24
24
|
|
|
25
25
|
const FLAG_NAMES = Object.keys(SUPPORTED_FLAGS);
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
const isNullish = (v) => v === null || v === undefined;
|
|
28
|
+
|
|
29
|
+
/** Per-type rules shared by built-in and extra flags. */
|
|
30
|
+
const FLAG_TYPES = Object.freeze({
|
|
31
|
+
string: {
|
|
32
|
+
normalise: (v, orDefault) =>
|
|
33
|
+
typeof v === 'string' && v.length > 0 ? v : orDefault(null),
|
|
34
|
+
isAbsent: (v) => isNullish(v) || v === '',
|
|
35
|
+
},
|
|
36
|
+
boolean: { normalise: (v) => v === true, isAbsent: (v) => !v },
|
|
37
|
+
ticket: { normalise: (v) => parseTicketId(v), isAbsent: isNullish },
|
|
38
|
+
integer: {
|
|
39
|
+
normalise: (v, orDefault) => (v === undefined ? orDefault(undefined) : v),
|
|
40
|
+
isAbsent: (v) => v === undefined || Number.isNaN(v),
|
|
41
|
+
},
|
|
42
|
+
'string-multi': {
|
|
43
|
+
normalise: (v, orDefault) => (Array.isArray(v) ? v : orDefault([])),
|
|
44
|
+
isAbsent: (v) => !Array.isArray(v) || v.length === 0,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
34
47
|
|
|
35
48
|
function camelCase(name) {
|
|
36
49
|
return name.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
|
|
@@ -50,16 +63,33 @@ function normaliseCallSignature(opts) {
|
|
|
50
63
|
return { argv: argv ?? [], schema, extras };
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
function
|
|
66
|
+
function flagEntries(schema, extras) {
|
|
67
|
+
const entries = Object.entries(SUPPORTED_FLAGS).map(
|
|
68
|
+
([flag, { key, type }]) => ({
|
|
69
|
+
flag,
|
|
70
|
+
key,
|
|
71
|
+
type,
|
|
72
|
+
def: {},
|
|
73
|
+
required: schema?.[flag]?.required === true,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
for (const [flag, def] of Object.entries(extras ?? {})) {
|
|
77
|
+
entries.push({
|
|
78
|
+
flag,
|
|
79
|
+
key: def.alias ?? camelCase(flag),
|
|
80
|
+
type: def.type,
|
|
81
|
+
def,
|
|
82
|
+
required: def.required === true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildDefineFlagsSpec(entries) {
|
|
54
89
|
const spec = {};
|
|
55
|
-
for (const
|
|
90
|
+
for (const { flag, key, type, def } of entries) {
|
|
56
91
|
spec[flag] = { type, alias: key };
|
|
57
|
-
|
|
58
|
-
if (!extras) return spec;
|
|
59
|
-
for (const [flag, def] of Object.entries(extras)) {
|
|
60
|
-
const entry = { type: def.type, alias: def.alias ?? camelCase(flag) };
|
|
61
|
-
if ('default' in def) entry.default = def.default;
|
|
62
|
-
spec[flag] = entry;
|
|
92
|
+
if ('default' in def) spec[flag].default = def.default;
|
|
63
93
|
}
|
|
64
94
|
return spec;
|
|
65
95
|
}
|
|
@@ -119,10 +149,10 @@ function validateExtras(extras) {
|
|
|
119
149
|
`parseStandardCliArgs: extras["${flag}"] must be an object`,
|
|
120
150
|
);
|
|
121
151
|
}
|
|
122
|
-
if (!
|
|
152
|
+
if (!Object.hasOwn(FLAG_TYPES, def.type)) {
|
|
123
153
|
const err = new Error(
|
|
124
154
|
`parseStandardCliArgs: extras["${flag}"].type "${def.type}" is unsupported. ` +
|
|
125
|
-
`Supported: ${
|
|
155
|
+
`Supported: ${Object.keys(FLAG_TYPES).join(', ')}.`,
|
|
126
156
|
);
|
|
127
157
|
err.code = 'UNKNOWN_EXTRAS_TYPE';
|
|
128
158
|
throw err;
|
|
@@ -130,15 +160,6 @@ function validateExtras(extras) {
|
|
|
130
160
|
}
|
|
131
161
|
}
|
|
132
162
|
|
|
133
|
-
function isAbsent(type, cur) {
|
|
134
|
-
if (type === 'ticket') return cur === null || cur === undefined;
|
|
135
|
-
if (type === 'string') return cur === null || cur === undefined || cur === '';
|
|
136
|
-
if (type === 'boolean') return !cur;
|
|
137
|
-
if (type === 'integer') return cur === undefined || Number.isNaN(cur);
|
|
138
|
-
if (type === 'string-multi') return !Array.isArray(cur) || cur.length === 0;
|
|
139
|
-
return cur === undefined || cur === null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
163
|
function throwMissing(flag) {
|
|
143
164
|
const err = new Error(
|
|
144
165
|
`parseStandardCliArgs: missing required flag --${flag}`,
|
|
@@ -148,56 +169,19 @@ function throwMissing(flag) {
|
|
|
148
169
|
throw err;
|
|
149
170
|
}
|
|
150
171
|
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
for (const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (isAbsent(meta.type, values[meta.key])) throwMissing(flag);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function enforceExtrasRequired(values, extras) {
|
|
161
|
-
if (!extras) return;
|
|
162
|
-
for (const [flag, def] of Object.entries(extras)) {
|
|
163
|
-
if (!def || def.required !== true) continue;
|
|
164
|
-
const key = def.alias ?? camelCase(flag);
|
|
165
|
-
if (isAbsent(def.type, values[key])) throwMissing(flag);
|
|
172
|
+
function normaliseValues(raw, entries) {
|
|
173
|
+
const out = {};
|
|
174
|
+
for (const { key, type, def } of entries) {
|
|
175
|
+
const orDefault = (empty) => ('default' in def ? def.default : empty);
|
|
176
|
+
out[key] = FLAG_TYPES[type].normalise(raw[key], orDefault);
|
|
166
177
|
}
|
|
178
|
+
return out;
|
|
167
179
|
}
|
|
168
180
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
for (const [, { key, type }] of Object.entries(SUPPORTED_FLAGS)) {
|
|
173
|
-
let v = raw[key];
|
|
174
|
-
if (type === 'ticket') {
|
|
175
|
-
v = parseTicketId(v);
|
|
176
|
-
} else if (type === 'string') {
|
|
177
|
-
v = typeof v === 'string' && v.length > 0 ? v : null;
|
|
178
|
-
} else if (type === 'boolean') {
|
|
179
|
-
v = v === true;
|
|
180
|
-
}
|
|
181
|
-
out[key] = v;
|
|
181
|
+
function enforceRequired(values, entries) {
|
|
182
|
+
for (const { flag, key, type, required } of entries) {
|
|
183
|
+
if (required && FLAG_TYPES[type].isAbsent(values[key])) throwMissing(flag);
|
|
182
184
|
}
|
|
183
|
-
if (!extras) return out;
|
|
184
|
-
for (const [flag, def] of Object.entries(extras)) {
|
|
185
|
-
const key = def.alias ?? camelCase(flag);
|
|
186
|
-
let v = raw[key];
|
|
187
|
-
if (def.type === 'boolean') {
|
|
188
|
-
v = v === true;
|
|
189
|
-
} else if (def.type === 'string') {
|
|
190
|
-
if (v === undefined) v = 'default' in def ? def.default : null;
|
|
191
|
-
} else if (def.type === 'ticket') {
|
|
192
|
-
v = parseTicketId(v);
|
|
193
|
-
} else if (def.type === 'string-multi') {
|
|
194
|
-
if (!Array.isArray(v)) v = 'default' in def ? def.default : [];
|
|
195
|
-
} else if (def.type === 'integer') {
|
|
196
|
-
if (v === undefined && 'default' in def) v = def.default;
|
|
197
|
-
}
|
|
198
|
-
out[key] = v;
|
|
199
|
-
}
|
|
200
|
-
return out;
|
|
201
185
|
}
|
|
202
186
|
|
|
203
187
|
/**
|
|
@@ -222,13 +206,13 @@ export function parseStandardCliArgs(opts = {}) {
|
|
|
222
206
|
err.flag = unknown;
|
|
223
207
|
throw err;
|
|
224
208
|
}
|
|
209
|
+
const entries = flagEntries(schema, extras);
|
|
225
210
|
const { values: raw, positionals } = defineFlags(
|
|
226
|
-
buildDefineFlagsSpec(
|
|
211
|
+
buildDefineFlagsSpec(entries),
|
|
227
212
|
argv,
|
|
228
213
|
);
|
|
229
|
-
const values = normaliseValues(raw,
|
|
230
|
-
enforceRequired(values,
|
|
231
|
-
enforceExtrasRequired(values, extras);
|
|
214
|
+
const values = normaliseValues(raw, entries);
|
|
215
|
+
enforceRequired(values, entries);
|
|
232
216
|
return { values, positionals };
|
|
233
217
|
}
|
|
234
218
|
|
|
@@ -133,6 +133,30 @@ function tolerantOverrideReviewBlock(value) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Best-effort: absent or junk is `null` plus a warning, never a throw.
|
|
138
|
+
*
|
|
139
|
+
* @param {unknown} value
|
|
140
|
+
* @returns {{ tokens: number|null, warning: string|null }}
|
|
141
|
+
*/
|
|
142
|
+
export function parseWorkerTokens(value) {
|
|
143
|
+
if (value == null) {
|
|
144
|
+
return {
|
|
145
|
+
tokens: null,
|
|
146
|
+
warning:
|
|
147
|
+
'--worker-tokens not supplied; telemetry.workerTokens records null.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const raw = String(value).trim();
|
|
151
|
+
if (/^\d+$/.test(raw) && Number.isSafeInteger(Number(raw))) {
|
|
152
|
+
return { tokens: Number(raw), warning: null };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
tokens: null,
|
|
156
|
+
warning: `--worker-tokens must be a non-negative integer (got ${JSON.stringify(value)}); telemetry.workerTokens records null.`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
136
160
|
/**
|
|
137
161
|
* Throws when a validating flag parser rejects a value; error handlers use
|
|
138
162
|
* {@link parseSprintArgsTolerant} instead of re-calling this.
|
|
@@ -164,6 +188,7 @@ export function parseSprintArgs(
|
|
|
164
188
|
'merge-watch-mode': { type: 'string' },
|
|
165
189
|
'rerun-advisory': { type: 'string' },
|
|
166
190
|
'override-review-block': { type: 'string' },
|
|
191
|
+
'worker-tokens': { type: 'string' },
|
|
167
192
|
executor: { type: 'string' },
|
|
168
193
|
cwd: { type: 'string' },
|
|
169
194
|
'recut-of': { type: 'string' },
|
|
@@ -194,6 +219,7 @@ export function parseSprintArgs(
|
|
|
194
219
|
overrideReviewBlock: tolerant
|
|
195
220
|
? tolerantOverrideReviewBlock(values['override-review-block'])
|
|
196
221
|
: parseOverrideReviewBlock(values['override-review-block']),
|
|
222
|
+
workerTokens: values['worker-tokens'],
|
|
197
223
|
executor: values.executor ?? null,
|
|
198
224
|
cwd:
|
|
199
225
|
(typeof values.cwd === 'string' && values.cwd.trim()) ||
|
|
@@ -23,7 +23,7 @@ export const LIST_OR_EXTENDER_OF_STRINGS = {
|
|
|
23
23
|
],
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
const TOLERANCE_SCHEMA = {
|
|
27
27
|
type: 'object',
|
|
28
28
|
description:
|
|
29
29
|
'How much a rollup may drift from the committed baseline before the gate reports a regression.',
|
|
@@ -47,7 +47,7 @@ export const TOLERANCE_SCHEMA = {
|
|
|
47
47
|
/**
|
|
48
48
|
* `"*"` is optional: the resolver injects the framework default when absent.
|
|
49
49
|
*/
|
|
50
|
-
|
|
50
|
+
const FLOORS_SCHEMA = {
|
|
51
51
|
type: 'object',
|
|
52
52
|
description:
|
|
53
53
|
'Workspace-keyed absolute floors: `{ "<workspace>": { "<metric>": number } }`. `"*"` is the project-wide catch-all; the metric keyset is open so per-rollup keys flow through without each gate enumerating them. Floors are absolute — unlike `tolerance`, they are enforced regardless of the baseline.',
|
|
@@ -57,7 +57,7 @@ export const FLOORS_SCHEMA = {
|
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const COMPONENTS_SCHEMA = {
|
|
61
61
|
type: 'object',
|
|
62
62
|
description:
|
|
63
63
|
'Per-gate component map — component name to the glob list whose files roll up under it. Defaults to `{ "*": ["**"] }` at the resolver layer.',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one default review chain: the factory's unset/empty fallback and the
|
|
3
|
+
* runtime schema's declared default. `code-review` is `optional` so hosts
|
|
4
|
+
* without the `claude` CLI close on `native` alone.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_REVIEW_PROVIDERS = Object.freeze([
|
|
7
|
+
Object.freeze({ name: 'native' }),
|
|
8
|
+
Object.freeze({
|
|
9
|
+
name: 'code-review',
|
|
10
|
+
scopes: Object.freeze(['story']),
|
|
11
|
+
optional: true,
|
|
12
|
+
}),
|
|
13
|
+
]);
|
|
@@ -140,7 +140,7 @@ const WORKTREE_ISOLATION_SCHEMA = {
|
|
|
140
140
|
const MERGE_WATCH_SCHEMA = {
|
|
141
141
|
type: 'object',
|
|
142
142
|
description:
|
|
143
|
-
"Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The
|
|
143
|
+
"Knobs consumed by the close-and-land merge wait (Story #4543). `mode` (Story #4698) selects the close-time merge posture. `maxWaitSeconds` bounds ONE invocation of the merge wait and its expiry returns a resumable `pending` terminal with no label mutation; `maxBudgetSeconds` bounds the CUMULATIVE wait across resumes (anchored at the PR's createdAt, so a resume does not restart the clock) and exhausting it is the genuine give-up that classifies and blocks. The poll cadence is internal (at most 30s while checks run, at most 10s once they are green and the PR is unmerged); it and the cap of 3 behind-the-base updates are not configurable.",
|
|
144
144
|
properties: {
|
|
145
145
|
mode: {
|
|
146
146
|
type: 'string',
|
|
@@ -173,7 +173,7 @@ const ROUTING_SCHEMA = {
|
|
|
173
173
|
roleScopedAgents: {
|
|
174
174
|
type: 'boolean',
|
|
175
175
|
description:
|
|
176
|
-
'Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full CLAUDE.md @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate.',
|
|
176
|
+
'Epic #4478 (M7-B). Kill-switch for the role-scoped boot contexts. When true (default), a converted delivery spawn (`story-worker`, `acceptance-critic`) boots on its own `.claude/agents/<role>.md` system prompt instead of re-paying the full entry-doc (AGENTS.md or CLAUDE.md) @-import closure. When false, every converted spawn falls back to `subagent_type: general-purpose` — the instant, code-rollback-free per-consumer revert, and the universal escape for hosts that ignore `.claude/agents/`. The fallback is the full-closure agent that ran before M7-B, so flipping it off never drops a gate.',
|
|
177
177
|
default: DELIVERY_ROUTING_DEFAULTS.roleScopedAgents,
|
|
178
178
|
},
|
|
179
179
|
ceremonyProfile: {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// live in `config/gates/`.
|
|
5
5
|
|
|
6
6
|
import { GATES_SCHEMA } from './config/gates/index.js';
|
|
7
|
+
import { DEFAULT_REVIEW_PROVIDERS } from './config/review-chain-default.js';
|
|
7
8
|
import { DEFAULT_CODE_REVIEW } from './config/runners.js';
|
|
8
9
|
|
|
9
10
|
const AUTO_REFRESH_SCHEMA = {
|
|
@@ -80,9 +81,15 @@ export const CODE_REVIEW_SCHEMA = {
|
|
|
80
81
|
properties: {
|
|
81
82
|
name: {
|
|
82
83
|
type: 'string',
|
|
83
|
-
enum: [
|
|
84
|
+
enum: [
|
|
85
|
+
'native',
|
|
86
|
+
'code-review',
|
|
87
|
+
'codex',
|
|
88
|
+
'security-review',
|
|
89
|
+
'ultrareview',
|
|
90
|
+
],
|
|
84
91
|
description:
|
|
85
|
-
'Registered provider key. Inline: native, codex, security-review. Manual-prompt: ultrareview.',
|
|
92
|
+
'Registered provider key. Inline: native, code-review, codex, security-review. Manual-prompt: ultrareview.',
|
|
86
93
|
},
|
|
87
94
|
scopes: {
|
|
88
95
|
type: 'array',
|
|
@@ -125,17 +132,8 @@ export const CODE_REVIEW_SCHEMA = {
|
|
|
125
132
|
additionalProperties: false,
|
|
126
133
|
},
|
|
127
134
|
description:
|
|
128
|
-
|
|
129
|
-
default:
|
|
130
|
-
{ name: 'native' },
|
|
131
|
-
{ name: 'security-review', scopes: ['story'], optional: true },
|
|
132
|
-
{
|
|
133
|
-
name: 'ultrareview',
|
|
134
|
-
scopes: ['story'],
|
|
135
|
-
manualPrompt: true,
|
|
136
|
-
when: { label: 'risk::high' },
|
|
137
|
-
},
|
|
138
|
-
],
|
|
135
|
+
"Review-provider chain (Story #2871). When unset or empty, falls back to this default: `native` (scoped lint + MI) then an optional story-scoped `code-review` — a low-effort `claude --print` bug review whose every finding is critical and halts close before auto-merge; hosts without the `claude` CLI skip it. `security-review` and `ultrareview` are opt-in. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain.",
|
|
136
|
+
default: DEFAULT_REVIEW_PROVIDERS,
|
|
139
137
|
},
|
|
140
138
|
autoFixSeverity: {
|
|
141
139
|
type: 'string',
|