@opengsd/gsd-core 1.5.0-rc.2 → 1.5.0-rc.4
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/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-writer.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-mempalace-curator.md +47 -0
- package/agents/gsd-nyquist-auditor.md +1 -0
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +1 -1
- package/agents/gsd-planner.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.md +55 -2
- package/agents/gsd-security-auditor.md +1 -0
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +13 -2
- package/bin/install.js +61 -64
- package/commands/gsd/mempalace-capture.md +71 -0
- package/commands/gsd/mempalace-recall.md +102 -0
- package/commands/gsd/ns-context.md +4 -2
- package/commands/gsd/progress.md +2 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +277 -95
- package/gsd-core/bin/lib/active-workstream-store.cjs +6 -0
- package/gsd-core/bin/lib/capability-activation.cjs +86 -0
- package/gsd-core/bin/lib/capability-registry.cjs +1468 -11
- package/gsd-core/bin/lib/capability-state.cjs +128 -21
- package/gsd-core/bin/lib/capability-writer.cjs +354 -0
- package/gsd-core/bin/lib/check-command-router.cjs +328 -1
- package/gsd-core/bin/lib/clusters.cjs +2 -0
- package/gsd-core/bin/lib/command-roster.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +33 -10
- package/gsd-core/bin/lib/config-loader.cjs +7 -8
- package/gsd-core/bin/lib/config-schema.cjs +32 -3
- package/gsd-core/bin/lib/config.cjs +81 -26
- package/gsd-core/bin/lib/core.cjs +5 -2
- package/gsd-core/bin/lib/edge-probe.cjs +25 -2
- package/gsd-core/bin/lib/frontmatter.cjs +53 -1
- package/gsd-core/bin/lib/git-base-branch.cjs +194 -0
- package/gsd-core/bin/lib/init.cjs +36 -11
- package/gsd-core/bin/lib/install-profiles.cjs +57 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +157 -16
- package/gsd-core/bin/lib/model-resolver.cjs +47 -5
- package/gsd-core/bin/lib/phase.cjs +99 -23
- package/gsd-core/bin/lib/plan-drift-guard.cjs +117 -0
- package/gsd-core/bin/lib/probe-core.cjs +117 -1
- package/gsd-core/bin/lib/profile-output.cjs +45 -4
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +13 -3
- package/gsd-core/bin/lib/roadmap.cjs +97 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +1946 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +54 -30
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +27 -19
- package/gsd-core/bin/lib/runtime-homes.cjs +26 -20
- package/gsd-core/bin/lib/state-command-router.cjs +15 -3
- package/gsd-core/bin/lib/state-document.cjs +46 -1
- package/gsd-core/bin/lib/state.cjs +461 -94
- package/gsd-core/bin/lib/verify.cjs +92 -8
- package/gsd-core/bin/lib/worktree-safety.cjs +2 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -2
- package/gsd-core/bin/shared/config-schema.manifest.json +0 -18
- package/gsd-core/bin/shared/model-catalog.json +1 -0
- package/gsd-core/references/edge-probe.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +61 -0
- package/gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
- package/gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
- package/gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
- package/gsd-core/references/prohibition-probe.md +248 -0
- package/gsd-core/templates/config.json +1 -1
- package/gsd-core/templates/spec.md +14 -0
- package/gsd-core/workflows/audit-milestone.md +5 -3
- package/gsd-core/workflows/autonomous.md +10 -5
- package/gsd-core/workflows/code-review-fix.md +9 -7
- package/gsd-core/workflows/code-review.md +8 -6
- package/gsd-core/workflows/complete-milestone.md +1 -5
- package/gsd-core/workflows/discuss-phase.md +14 -0
- package/gsd-core/workflows/execute-phase.md +86 -146
- package/gsd-core/workflows/execute-plan.md +21 -6
- package/gsd-core/workflows/help/modes/full.md +7 -1
- package/gsd-core/workflows/new-project.md +3 -3
- package/gsd-core/workflows/next.md +50 -2
- package/gsd-core/workflows/pause-work.md +7 -1
- package/gsd-core/workflows/plan-phase.md +91 -221
- package/gsd-core/workflows/plan-review-convergence.md +14 -4
- package/gsd-core/workflows/pr-branch.md +4 -2
- package/gsd-core/workflows/profile-user.md +3 -1
- package/gsd-core/workflows/progress.md +58 -1
- package/gsd-core/workflows/quick.md +12 -8
- package/gsd-core/workflows/resume-project.md +17 -1
- package/gsd-core/workflows/review.md +19 -2
- package/gsd-core/workflows/secure-phase.md +4 -2
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings.md +27 -1
- package/gsd-core/workflows/ship.md +58 -5
- package/gsd-core/workflows/spec-phase.md +75 -0
- package/gsd-core/workflows/validate-phase.md +4 -2
- package/gsd-core/workflows/verify-phase.md +14 -4
- package/gsd-core/workflows/verify-work.md +27 -11
- package/hooks/dist/gsd-ensure-canonical-path.js +305 -0
- package/hooks/dist/gsd-statusline.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-ensure-canonical-path.js +305 -0
- package/hooks/gsd-statusline.js +1 -1
- package/hooks/hooks.json +1 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -4
- package/scripts/affected-tests-lib.cjs +16 -4
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/new.cjs +17 -3
- package/scripts/fix-slash-commands.cjs +15 -3
- package/scripts/gen-capability-registry.cjs +373 -49
- package/scripts/gen-inventory-manifest.cjs +1 -4
- package/scripts/gen-loop-host-contract.cjs +55 -0
- package/scripts/issue-version-gate.cjs +140 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +327 -0
- package/scripts/lint-allow-test-rule-refs.cjs +162 -0
- package/scripts/lint-test-file-count.allowlist.json +14 -0
- package/scripts/mutation-matrix.cjs +108 -7
- package/scripts/pr-target-policy.cjs +63 -0
- package/scripts/release-tarball-smoke.cjs +7 -1
- package/scripts/research-profiles.cjs +5 -5
- package/scripts/run-tests.cjs +178 -17
|
@@ -24,6 +24,8 @@ const frontmatterMod = require("./frontmatter.cjs");
|
|
|
24
24
|
const stateMod = require("./state.cjs");
|
|
25
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-profiles.cjs is an export= CommonJS module
|
|
26
26
|
const modelProfilesMod = require("./model-profiles.cjs");
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
28
|
+
const planScanMod = require("./plan-scan.cjs");
|
|
27
29
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
30
|
const package_identity_cjs_1 = require("./package-identity.cjs");
|
|
29
31
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
@@ -450,6 +452,39 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
450
452
|
artifacts: results,
|
|
451
453
|
}, raw, passed === results.length ? 'valid' : 'invalid');
|
|
452
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Returns a Set of file paths (relative to cwd) that are promised by plans in
|
|
457
|
+
* the same phase directory at a wave number >= minWave.
|
|
458
|
+
*
|
|
459
|
+
* Used by cmdVerifyKeyLinks to avoid hard-failing a missing `from:` file that
|
|
460
|
+
* is a planned future artifact (fix #1202).
|
|
461
|
+
*/
|
|
462
|
+
function collectPromisedFilesAtOrAfterWave(phaseDir, minWave) {
|
|
463
|
+
const promised = new Set();
|
|
464
|
+
const { planFiles } = planScanMod.scanPhasePlans(phaseDir);
|
|
465
|
+
for (const planFile of planFiles) {
|
|
466
|
+
const planFullPath = node_path_1.default.join(phaseDir, planFile);
|
|
467
|
+
const planContent = (0, shell_command_projection_cjs_1.platformReadSync)(planFullPath);
|
|
468
|
+
if (!planContent)
|
|
469
|
+
continue;
|
|
470
|
+
const fm = extractFrontmatter(planContent);
|
|
471
|
+
const waveRaw = fm['wave'];
|
|
472
|
+
const wave = typeof waveRaw === 'string' ? parseInt(waveRaw, 10) : (typeof waveRaw === 'number' ? waveRaw : NaN);
|
|
473
|
+
if (isNaN(wave) || wave < minWave)
|
|
474
|
+
continue;
|
|
475
|
+
const filesModified = fm['files_modified'];
|
|
476
|
+
if (!filesModified)
|
|
477
|
+
continue;
|
|
478
|
+
const files = Array.isArray(filesModified)
|
|
479
|
+
? filesModified
|
|
480
|
+
: (typeof filesModified === 'string' ? [filesModified] : []);
|
|
481
|
+
for (const f of files) {
|
|
482
|
+
if (typeof f === 'string' && f.trim())
|
|
483
|
+
promised.add(f.trim());
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return promised;
|
|
487
|
+
}
|
|
453
488
|
function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
454
489
|
if (!planFilePath) {
|
|
455
490
|
error('plan file path required');
|
|
@@ -465,7 +500,25 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
465
500
|
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw);
|
|
466
501
|
return;
|
|
467
502
|
}
|
|
503
|
+
// Derive the current plan's wave number and phase directory for wave-aware
|
|
504
|
+
// missing-file handling (fix #1202).
|
|
505
|
+
const currentFm = extractFrontmatter(content);
|
|
506
|
+
const currentWaveRaw = currentFm['wave'];
|
|
507
|
+
const currentWave = typeof currentWaveRaw === 'string'
|
|
508
|
+
? parseInt(currentWaveRaw, 10)
|
|
509
|
+
: (typeof currentWaveRaw === 'number' ? currentWaveRaw : 1);
|
|
510
|
+
const phaseDir = node_path_1.default.dirname(fullPath);
|
|
511
|
+
// Collect files promised by plans at wave >= currentWave (lazy: computed once
|
|
512
|
+
// the first time a missing source is encountered).
|
|
513
|
+
let promisedFiles = null;
|
|
514
|
+
function getPromisedFiles() {
|
|
515
|
+
if (promisedFiles === null) {
|
|
516
|
+
promisedFiles = collectPromisedFilesAtOrAfterWave(phaseDir, isNaN(currentWave) ? 1 : currentWave);
|
|
517
|
+
}
|
|
518
|
+
return promisedFiles;
|
|
519
|
+
}
|
|
468
520
|
const results = [];
|
|
521
|
+
let pendingCount = 0;
|
|
469
522
|
for (const link of keyLinks) {
|
|
470
523
|
if (typeof link === 'string')
|
|
471
524
|
continue;
|
|
@@ -476,9 +529,20 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
476
529
|
verified: false,
|
|
477
530
|
detail: '',
|
|
478
531
|
};
|
|
479
|
-
const
|
|
532
|
+
const fromPath = link['from'] || '';
|
|
533
|
+
const sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, fromPath));
|
|
480
534
|
if (!sourceContent) {
|
|
481
|
-
|
|
535
|
+
// Check if the missing file is promised by a plan at the same or later wave.
|
|
536
|
+
const promised = getPromisedFiles();
|
|
537
|
+
const isPromised = fromPath.trim() !== '' && promised.has(fromPath.trim());
|
|
538
|
+
if (isPromised) {
|
|
539
|
+
check['pending'] = true;
|
|
540
|
+
check['detail'] = 'Source file not yet created — declared in files_modified of a same-or-later-wave plan';
|
|
541
|
+
pendingCount++;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
check['detail'] = 'Source file not found (from: must be a relative file path; describe components/endpoints in via:)';
|
|
545
|
+
}
|
|
482
546
|
}
|
|
483
547
|
else if (link['pattern']) {
|
|
484
548
|
try {
|
|
@@ -514,12 +578,17 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
514
578
|
results.push(check);
|
|
515
579
|
}
|
|
516
580
|
const verified = results.filter((r) => r['verified']).length;
|
|
581
|
+
// A pending link (from: file promised by a same-or-later-wave plan) is not a
|
|
582
|
+
// hard failure — it should not count against the all_verified gate (#1202).
|
|
583
|
+
const hardFailed = results.filter((r) => !r['verified'] && !r['pending']).length;
|
|
584
|
+
const allVerified = hardFailed === 0;
|
|
517
585
|
output({
|
|
518
|
-
all_verified:
|
|
586
|
+
all_verified: allVerified,
|
|
519
587
|
verified,
|
|
588
|
+
pending: pendingCount,
|
|
520
589
|
total: results.length,
|
|
521
590
|
links: results,
|
|
522
|
-
}, raw,
|
|
591
|
+
}, raw, allVerified ? 'valid' : 'invalid');
|
|
523
592
|
}
|
|
524
593
|
function listMilestoneArchiveDirs(planBase) {
|
|
525
594
|
const milestonesDir = node_path_1.default.join(planBase, 'milestones');
|
|
@@ -1352,7 +1421,7 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1352
1421
|
const pDir = planningDir(cwd);
|
|
1353
1422
|
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1354
1423
|
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1355
|
-
output({ drift_detected: false, blocking: false, message: 'No phases directory' }, raw);
|
|
1424
|
+
output({ block: false, drift_detected: false, blocking: false, message: 'No phases directory' }, raw);
|
|
1356
1425
|
return;
|
|
1357
1426
|
}
|
|
1358
1427
|
let phaseDir = null;
|
|
@@ -1369,7 +1438,7 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1369
1438
|
phaseDir = exact;
|
|
1370
1439
|
}
|
|
1371
1440
|
if (!phaseDir) {
|
|
1372
|
-
output({ drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1441
|
+
output({ block: false, drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1373
1442
|
return;
|
|
1374
1443
|
}
|
|
1375
1444
|
const allFiles = [];
|
|
@@ -1392,14 +1461,20 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1392
1461
|
executionLog += '\n' + gitLog.stdout;
|
|
1393
1462
|
}
|
|
1394
1463
|
const result = (0, schema_detect_cjs_1.checkSchemaDrift)(allFiles, executionLog, { skipCheck: !!skipFlag });
|
|
1464
|
+
const isSkipped = !!result['skipped'];
|
|
1395
1465
|
output({
|
|
1466
|
+
// Uniform gate contract: `block` = true means "this gate's bad condition is met".
|
|
1467
|
+
// When skipCheck is true (GSD_SKIP_SCHEMA_CHECK=true), the gate is bypassed —
|
|
1468
|
+
// block must be false regardless of whether drift was detected.
|
|
1469
|
+
// drift_detected and blocking are kept for compatibility.
|
|
1470
|
+
block: isSkipped ? false : !!result['driftDetected'],
|
|
1396
1471
|
drift_detected: result['driftDetected'],
|
|
1397
1472
|
blocking: result['blocking'],
|
|
1398
1473
|
schema_files: result['schemaFiles'],
|
|
1399
1474
|
orms: result['orms'],
|
|
1400
1475
|
unpushed_orms: result['unpushedOrms'],
|
|
1401
1476
|
message: result['message'],
|
|
1402
|
-
skipped:
|
|
1477
|
+
skipped: isSkipped,
|
|
1403
1478
|
}, raw);
|
|
1404
1479
|
}
|
|
1405
1480
|
function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
@@ -1412,6 +1487,8 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1412
1487
|
const structurePath = node_path_1.default.join(codebaseDir, 'STRUCTURE.md');
|
|
1413
1488
|
if (!node_fs_1.default.existsSync(structurePath)) {
|
|
1414
1489
|
emit({
|
|
1490
|
+
// Uniform gate contract: block = action_required (false when skipped).
|
|
1491
|
+
block: false,
|
|
1415
1492
|
skipped: true,
|
|
1416
1493
|
reason: 'no-structure-md',
|
|
1417
1494
|
action_required: false,
|
|
@@ -1426,6 +1503,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1426
1503
|
}
|
|
1427
1504
|
catch (err) {
|
|
1428
1505
|
emit({
|
|
1506
|
+
block: false,
|
|
1429
1507
|
skipped: true,
|
|
1430
1508
|
reason: 'cannot-read-structure-md: ' + (err instanceof Error ? err.message : String(err)),
|
|
1431
1509
|
action_required: false,
|
|
@@ -1438,6 +1516,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1438
1516
|
const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
|
|
1439
1517
|
if (revProbe.exitCode !== 0) {
|
|
1440
1518
|
emit({
|
|
1519
|
+
block: false,
|
|
1441
1520
|
skipped: true,
|
|
1442
1521
|
reason: 'not-a-git-repo',
|
|
1443
1522
|
action_required: false,
|
|
@@ -1459,6 +1538,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1459
1538
|
const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
|
|
1460
1539
|
if (diff.exitCode !== 0) {
|
|
1461
1540
|
emit({
|
|
1541
|
+
block: false,
|
|
1462
1542
|
skipped: true,
|
|
1463
1543
|
reason: 'git-diff-failed',
|
|
1464
1544
|
action_required: false,
|
|
@@ -1500,10 +1580,13 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1500
1580
|
action,
|
|
1501
1581
|
runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd),
|
|
1502
1582
|
});
|
|
1583
|
+
const actionRequired = !!driftResult['actionRequired'];
|
|
1503
1584
|
emit({
|
|
1585
|
+
// Uniform gate contract: block = action_required.
|
|
1586
|
+
block: actionRequired,
|
|
1504
1587
|
skipped: !!driftResult['skipped'],
|
|
1505
1588
|
reason: driftResult['reason'] || null,
|
|
1506
|
-
action_required:
|
|
1589
|
+
action_required: actionRequired,
|
|
1507
1590
|
directive: driftResult['directive'],
|
|
1508
1591
|
spawn_mapper: !!driftResult['spawnMapper'],
|
|
1509
1592
|
affected_paths: driftResult['affectedPaths'] || [],
|
|
@@ -1516,6 +1599,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1516
1599
|
}
|
|
1517
1600
|
catch (err) {
|
|
1518
1601
|
emit({
|
|
1602
|
+
block: false,
|
|
1519
1603
|
skipped: true,
|
|
1520
1604
|
reason: 'exception: ' + (err && err instanceof Error ? err.message : String(err)),
|
|
1521
1605
|
action_required: false,
|
|
@@ -701,6 +701,7 @@ function reapOrphanWorktrees(repoRoot, deps = {}) {
|
|
|
701
701
|
const readFileSafe = deps.readFileSafe || defaultReadFileSafe;
|
|
702
702
|
const mtimeSafe = deps.mtimeSafe || defaultMtimeSafe;
|
|
703
703
|
const reapMtimeGuardMs = deps.reapMtimeGuardMs !== undefined ? deps.reapMtimeGuardMs : REAP_MTIME_GUARD_MS;
|
|
704
|
+
const nowMs = deps.nowMs ?? Date.now();
|
|
704
705
|
const results = [];
|
|
705
706
|
// 1. Discover the .git/worktrees/ admin directory.
|
|
706
707
|
const gitDir = execGit(['rev-parse', '--git-dir'], { cwd: repoRoot });
|
|
@@ -803,7 +804,7 @@ function reapOrphanWorktrees(repoRoot, deps = {}) {
|
|
|
803
804
|
}
|
|
804
805
|
// 4a. Stale-lock guard: skip if lock is too fresh (PID recycling / race).
|
|
805
806
|
const lockMtime = mtimeSafe(lockedFile);
|
|
806
|
-
if (!lockMtime ||
|
|
807
|
+
if (!lockMtime || nowMs - lockMtime.getTime() < reapMtimeGuardMs) {
|
|
807
808
|
results.push({ path: worktreePath, status: 'skipped', reason: 'lock_too_fresh' });
|
|
808
809
|
continue;
|
|
809
810
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"project_code": null,
|
|
14
14
|
"phase_id_convention": null,
|
|
15
15
|
"mode": "interactive",
|
|
16
|
-
"claude_md_path": "
|
|
16
|
+
"claude_md_path": "./.claude/CLAUDE.md",
|
|
17
17
|
"git": {
|
|
18
18
|
"branching_strategy": "none",
|
|
19
19
|
"create_tag": true,
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"verifier": true,
|
|
29
29
|
"nyquist_validation": true,
|
|
30
30
|
"ai_integration_phase": true,
|
|
31
|
-
"tdd_mode": false,
|
|
32
31
|
"human_verify_mode": "end-of-phase",
|
|
33
32
|
"auto_advance": false,
|
|
34
33
|
"_auto_chain_active": false,
|
|
@@ -10,17 +10,11 @@
|
|
|
10
10
|
"brave_search",
|
|
11
11
|
"firecrawl",
|
|
12
12
|
"exa_search",
|
|
13
|
-
"workflow.research",
|
|
14
13
|
"workflow.plan_check",
|
|
15
14
|
"workflow.verifier",
|
|
16
|
-
"workflow.nyquist_validation",
|
|
17
|
-
"workflow.ai_integration_phase",
|
|
18
|
-
"workflow.ui_phase",
|
|
19
|
-
"workflow.ui_safety_gate",
|
|
20
15
|
"workflow.auto_advance",
|
|
21
16
|
"workflow.node_repair",
|
|
22
17
|
"workflow.node_repair_budget",
|
|
23
|
-
"workflow.tdd_mode",
|
|
24
18
|
"workflow.human_verify_mode",
|
|
25
19
|
"workflow.text_mode",
|
|
26
20
|
"workflow.research_before_questions",
|
|
@@ -29,21 +23,12 @@
|
|
|
29
23
|
"workflow.auto_prune_state",
|
|
30
24
|
"workflow.use_worktrees",
|
|
31
25
|
"workflow.worktree_skip_hooks",
|
|
32
|
-
"workflow.code_review",
|
|
33
|
-
"workflow.code_review_depth",
|
|
34
26
|
"workflow.code_review_command",
|
|
35
|
-
"workflow.pattern_mapper",
|
|
36
27
|
"workflow.plan_bounce",
|
|
37
28
|
"workflow.plan_bounce_script",
|
|
38
29
|
"workflow.plan_bounce_passes",
|
|
39
30
|
"workflow.plan_chunked",
|
|
40
31
|
"workflow.plan_review_convergence",
|
|
41
|
-
"workflow.post_planning_gaps",
|
|
42
|
-
"workflow.security_enforcement",
|
|
43
|
-
"workflow.security_asvs_level",
|
|
44
|
-
"workflow.security_block_on",
|
|
45
|
-
"workflow.drift_threshold",
|
|
46
|
-
"workflow.drift_action",
|
|
47
32
|
"code_quality.fallow.enabled",
|
|
48
33
|
"code_quality.fallow.scope",
|
|
49
34
|
"code_quality.fallow.profile",
|
|
@@ -76,7 +61,6 @@
|
|
|
76
61
|
"workflow.context_coverage_gate",
|
|
77
62
|
"statusline.show_last_command",
|
|
78
63
|
"statusline.context_position",
|
|
79
|
-
"workflow.ui_review",
|
|
80
64
|
"workflow.max_discuss_passes",
|
|
81
65
|
"features.thinking_partner",
|
|
82
66
|
"context",
|
|
@@ -90,8 +74,6 @@
|
|
|
90
74
|
"manager.flags.execute",
|
|
91
75
|
"response_language",
|
|
92
76
|
"context_window",
|
|
93
|
-
"intel.enabled",
|
|
94
|
-
"graphify.enabled",
|
|
95
77
|
"graphify.build_timeout",
|
|
96
78
|
"graphify.auto_update",
|
|
97
79
|
"claude_md_path",
|
|
@@ -153,6 +153,7 @@
|
|
|
153
153
|
"gsd-eval-planner": { "golden": "opus", "balanced": "opus", "budget": "sonnet", "phaseType": "planning", "routingTier": "heavy" },
|
|
154
154
|
"gsd-framework-selector": { "golden": "opus", "balanced": "sonnet", "budget": "sonnet", "phaseType": "planning", "routingTier": "heavy" },
|
|
155
155
|
"gsd-intel-updater": { "golden": "opus", "balanced": "sonnet", "budget": "haiku", "phaseType": "research", "routingTier": "light" },
|
|
156
|
+
"gsd-mempalace-curator": { "golden": "sonnet", "balanced": "sonnet", "budget": "haiku", "phaseType": "research", "routingTier": "light" },
|
|
156
157
|
"gsd-security-auditor": { "golden": "opus", "balanced": "sonnet", "budget": "sonnet", "phaseType": "verification", "routingTier": "heavy" },
|
|
157
158
|
"gsd-user-profiler": { "golden": "opus", "balanced": "sonnet", "budget": "sonnet", "phaseType": "research", "routingTier": "heavy" }
|
|
158
159
|
}
|
|
@@ -77,6 +77,17 @@ Two rules keep the probe honest and prevent an "everything is N/A" failure mode:
|
|
|
77
77
|
2. **Dismissal requires a reason string.** "N/A — input is a bounded enum, no boundary
|
|
78
78
|
exists" is valid; silence is not. The reason string is the audit trail.
|
|
79
79
|
|
|
80
|
+
**Zero-classification surfaces an `unclassified` candidate (#1110).** The relevance filter is
|
|
81
|
+
a heuristic over prose cues, so a requirement whose wording *is* edge-relevant but matches no
|
|
82
|
+
shape cue would otherwise classify to zero shapes → zero edges and vanish from coverage with
|
|
83
|
+
no signal — the same silent blind spot the probe exists to catch. Instead, a requirement with
|
|
84
|
+
non-empty prose, no authored `shapes`, and zero matched shapes surfaces exactly one soft
|
|
85
|
+
`unclassified — review manually` candidate (`category: "unclassified"`, `status: "unresolved"`).
|
|
86
|
+
It is a dismissible nudge — resolve it, or dismiss it with a reason (e.g. a genuinely edge-free
|
|
87
|
+
static-asset requirement) — never a hard block. `unclassified` is a review signal, **not** a
|
|
88
|
+
ninth taxonomy category: the closed eight above are unchanged, and an explicit `shapes: []`
|
|
89
|
+
opt-out stays silent (the author's deliberate "no edge surface").
|
|
90
|
+
|
|
80
91
|
Each raised edge carries two orthogonal axes — a resolution **lifecycle** and, when
|
|
81
92
|
resolved, a **verification** tier (ADR-550 Decision 7, the shared probe-core model):
|
|
82
93
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Loop Hook Dispatch Contract
|
|
2
|
+
|
|
3
|
+
Generic reference for consuming the `--raw` JSON output of `gsd_run loop render-hooks <point>`
|
|
4
|
+
in any host-loop workflow. This document is point-agnostic — it applies to every loop
|
|
5
|
+
extension point (discuss:pre, discuss:post, plan:pre, plan:post, execute:pre, execute:wave:pre,
|
|
6
|
+
execute:wave:post, execute:post, verify:pre, verify:post, ship:pre, ship:post).
|
|
7
|
+
|
|
8
|
+
## Envelope shape
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"point": "discuss:pre",
|
|
13
|
+
"activeHooks": [
|
|
14
|
+
{ "kind": "contribution", "into": "orchestrator", "fragment": { "inline": "..." } },
|
|
15
|
+
{ "kind": "step", "ref": { "skill": "my-skill" } },
|
|
16
|
+
{ "kind": "gate", "check": { "query": "..." }, "blocking": true, "onError": "skip" }
|
|
17
|
+
],
|
|
18
|
+
"rendered": "..."
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`activeHooks` is an array of enabled hook entries for the named point. It is empty (or absent)
|
|
23
|
+
when no capability has registered an active hook at this point — treat that as a no-op.
|
|
24
|
+
|
|
25
|
+
## Dispatch rules by `kind`
|
|
26
|
+
|
|
27
|
+
### `contribution`
|
|
28
|
+
|
|
29
|
+
Inject `fragment.inline` verbatim into the context for the role named in `into`
|
|
30
|
+
(e.g. `orchestrator`, `planner`). Do not paraphrase — the text is the product.
|
|
31
|
+
|
|
32
|
+
### `step`
|
|
33
|
+
|
|
34
|
+
Dispatch the referenced unit:
|
|
35
|
+
|
|
36
|
+
- `ref.skill` present → dispatch via the Skill tool with skill id `gsd-<ref.skill>`.
|
|
37
|
+
- `ref.agent` present → dispatch via the Agent tool with `subagent_type` = `ref.agent`.
|
|
38
|
+
Before dispatching an agent, print the canonical liveness banner so users know silence
|
|
39
|
+
is expected and do not kill a healthy agent:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
◆ Spawning <agent>... (runs in a subagent — no output until it returns; expected, not a freeze)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Wait for the result before continuing to the next hook or the next step.
|
|
46
|
+
|
|
47
|
+
### `gate`
|
|
48
|
+
|
|
49
|
+
Evaluate `check` (one of `query`, `predicate`, or `agentVerdict`). Then honor `blocking`:
|
|
50
|
+
|
|
51
|
+
- `blocking: true` → if the check returns `block: true`, surface `check.message` to the user
|
|
52
|
+
and stop the current step. Do not continue.
|
|
53
|
+
- `blocking: false` → advisory only; surface the message but continue regardless of outcome.
|
|
54
|
+
|
|
55
|
+
Honor `onError` if the check itself errors: `skip` means treat as non-blocking and continue;
|
|
56
|
+
`fail` means surface the error and stop.
|
|
57
|
+
|
|
58
|
+
## Empty / absent `activeHooks`
|
|
59
|
+
|
|
60
|
+
If `activeHooks` is absent, null, or an empty array, skip silently and continue to the next
|
|
61
|
+
step in the workflow. No output to the user is needed.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{
|
|
4
|
+
"requirement_id": "R1",
|
|
5
|
+
"category": "values",
|
|
6
|
+
"status": "resolved",
|
|
7
|
+
"verification": "judgment",
|
|
8
|
+
"resolution": null,
|
|
9
|
+
"reason": null,
|
|
10
|
+
"statement": "MUST NOT use shaming, guilt, or loss-aversion streak framing (e.g. \"Don't lose your streak!\") — the reminder must encourage without penalty framing"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"coverage": { "applicable": 1, "resolved": 1, "unresolved": 0, "byVerification": { "test": 0, "judgment": 1 } }
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"items": [
|
|
3
|
+
{
|
|
4
|
+
"requirement_id": "R1",
|
|
5
|
+
"category": "fairness",
|
|
6
|
+
"status": "resolved",
|
|
7
|
+
"verification": "judgment",
|
|
8
|
+
"resolution": null,
|
|
9
|
+
"reason": null,
|
|
10
|
+
"statement": "MUST NOT use protected attributes (race, gender, age, national origin) or their proxies (zip code, name) in the loan decision or rate"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"requirement_id": "R1",
|
|
14
|
+
"category": "privacy",
|
|
15
|
+
"status": "resolved",
|
|
16
|
+
"verification": "test",
|
|
17
|
+
"resolution": null,
|
|
18
|
+
"reason": null,
|
|
19
|
+
"statement": "MUST NOT store raw PII / financial secrets (SSN, full account or card numbers) in plaintext in the audit log"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"requirement_id": "R1",
|
|
23
|
+
"category": "transparency",
|
|
24
|
+
"status": "resolved",
|
|
25
|
+
"verification": "judgment",
|
|
26
|
+
"resolution": null,
|
|
27
|
+
"reason": null,
|
|
28
|
+
"statement": "MUST NOT mislead or omit the true rate/APR/terms in the explanation; an adverse decision must state the real principal reason (adverse-action)"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"coverage": { "applicable": 3, "resolved": 3, "unresolved": 0, "byVerification": { "test": 1, "judgment": 2 } }
|
|
32
|
+
}
|