@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.
Files changed (131) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/gsd-advisor-researcher.md +1 -1
  3. package/agents/gsd-assumptions-analyzer.md +1 -1
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-code-reviewer.md +1 -1
  6. package/agents/gsd-codebase-mapper.md +1 -1
  7. package/agents/gsd-debugger.md +1 -1
  8. package/agents/gsd-doc-writer.md +1 -1
  9. package/agents/gsd-eval-auditor.md +1 -1
  10. package/agents/gsd-executor.md +1 -1
  11. package/agents/gsd-integration-checker.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +47 -0
  13. package/agents/gsd-nyquist-auditor.md +1 -0
  14. package/agents/gsd-phase-researcher.md +1 -1
  15. package/agents/gsd-plan-checker.md +1 -1
  16. package/agents/gsd-planner.md +1 -1
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +1 -1
  19. package/agents/gsd-roadmapper.md +55 -2
  20. package/agents/gsd-security-auditor.md +1 -0
  21. package/agents/gsd-ui-auditor.md +1 -1
  22. package/agents/gsd-ui-checker.md +1 -1
  23. package/agents/gsd-ui-researcher.md +1 -1
  24. package/agents/gsd-verifier.md +13 -2
  25. package/bin/install.js +61 -64
  26. package/commands/gsd/mempalace-capture.md +71 -0
  27. package/commands/gsd/mempalace-recall.md +102 -0
  28. package/commands/gsd/ns-context.md +4 -2
  29. package/commands/gsd/progress.md +2 -1
  30. package/gemini-extension.json +1 -1
  31. package/gsd-core/bin/gsd-tools.cjs +277 -95
  32. package/gsd-core/bin/lib/active-workstream-store.cjs +6 -0
  33. package/gsd-core/bin/lib/capability-activation.cjs +86 -0
  34. package/gsd-core/bin/lib/capability-registry.cjs +1468 -11
  35. package/gsd-core/bin/lib/capability-state.cjs +128 -21
  36. package/gsd-core/bin/lib/capability-writer.cjs +354 -0
  37. package/gsd-core/bin/lib/check-command-router.cjs +328 -1
  38. package/gsd-core/bin/lib/clusters.cjs +2 -0
  39. package/gsd-core/bin/lib/command-roster.cjs +19 -0
  40. package/gsd-core/bin/lib/commands.cjs +33 -10
  41. package/gsd-core/bin/lib/config-loader.cjs +7 -8
  42. package/gsd-core/bin/lib/config-schema.cjs +32 -3
  43. package/gsd-core/bin/lib/config.cjs +81 -26
  44. package/gsd-core/bin/lib/core.cjs +5 -2
  45. package/gsd-core/bin/lib/edge-probe.cjs +25 -2
  46. package/gsd-core/bin/lib/frontmatter.cjs +53 -1
  47. package/gsd-core/bin/lib/git-base-branch.cjs +194 -0
  48. package/gsd-core/bin/lib/init.cjs +36 -11
  49. package/gsd-core/bin/lib/install-profiles.cjs +57 -1
  50. package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
  51. package/gsd-core/bin/lib/loop-resolver.cjs +157 -16
  52. package/gsd-core/bin/lib/model-resolver.cjs +47 -5
  53. package/gsd-core/bin/lib/phase.cjs +99 -23
  54. package/gsd-core/bin/lib/plan-drift-guard.cjs +117 -0
  55. package/gsd-core/bin/lib/probe-core.cjs +117 -1
  56. package/gsd-core/bin/lib/profile-output.cjs +45 -4
  57. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +138 -0
  58. package/gsd-core/bin/lib/roadmap-parser.cjs +13 -3
  59. package/gsd-core/bin/lib/roadmap.cjs +97 -7
  60. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +1946 -0
  61. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +54 -30
  62. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +27 -19
  63. package/gsd-core/bin/lib/runtime-homes.cjs +26 -20
  64. package/gsd-core/bin/lib/state-command-router.cjs +15 -3
  65. package/gsd-core/bin/lib/state-document.cjs +46 -1
  66. package/gsd-core/bin/lib/state.cjs +461 -94
  67. package/gsd-core/bin/lib/verify.cjs +92 -8
  68. package/gsd-core/bin/lib/worktree-safety.cjs +2 -1
  69. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -2
  70. package/gsd-core/bin/shared/config-schema.manifest.json +0 -18
  71. package/gsd-core/bin/shared/model-catalog.json +1 -0
  72. package/gsd-core/references/edge-probe.md +11 -0
  73. package/gsd-core/references/loop-hook-dispatch.md +61 -0
  74. package/gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json +14 -0
  75. package/gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json +4 -0
  76. package/gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json +32 -0
  77. package/gsd-core/references/prohibition-probe.md +248 -0
  78. package/gsd-core/templates/config.json +1 -1
  79. package/gsd-core/templates/spec.md +14 -0
  80. package/gsd-core/workflows/audit-milestone.md +5 -3
  81. package/gsd-core/workflows/autonomous.md +10 -5
  82. package/gsd-core/workflows/code-review-fix.md +9 -7
  83. package/gsd-core/workflows/code-review.md +8 -6
  84. package/gsd-core/workflows/complete-milestone.md +1 -5
  85. package/gsd-core/workflows/discuss-phase.md +14 -0
  86. package/gsd-core/workflows/execute-phase.md +86 -146
  87. package/gsd-core/workflows/execute-plan.md +21 -6
  88. package/gsd-core/workflows/help/modes/full.md +7 -1
  89. package/gsd-core/workflows/new-project.md +3 -3
  90. package/gsd-core/workflows/next.md +50 -2
  91. package/gsd-core/workflows/pause-work.md +7 -1
  92. package/gsd-core/workflows/plan-phase.md +91 -221
  93. package/gsd-core/workflows/plan-review-convergence.md +14 -4
  94. package/gsd-core/workflows/pr-branch.md +4 -2
  95. package/gsd-core/workflows/profile-user.md +3 -1
  96. package/gsd-core/workflows/progress.md +58 -1
  97. package/gsd-core/workflows/quick.md +12 -8
  98. package/gsd-core/workflows/resume-project.md +17 -1
  99. package/gsd-core/workflows/review.md +19 -2
  100. package/gsd-core/workflows/secure-phase.md +4 -2
  101. package/gsd-core/workflows/settings-advanced.md +2 -0
  102. package/gsd-core/workflows/settings.md +27 -1
  103. package/gsd-core/workflows/ship.md +58 -5
  104. package/gsd-core/workflows/spec-phase.md +75 -0
  105. package/gsd-core/workflows/validate-phase.md +4 -2
  106. package/gsd-core/workflows/verify-phase.md +14 -4
  107. package/gsd-core/workflows/verify-work.md +27 -11
  108. package/hooks/dist/gsd-ensure-canonical-path.js +305 -0
  109. package/hooks/dist/gsd-statusline.js +1 -1
  110. package/hooks/dist/managed-hooks-registry.cjs +1 -0
  111. package/hooks/gsd-ensure-canonical-path.js +305 -0
  112. package/hooks/gsd-statusline.js +1 -1
  113. package/hooks/hooks.json +1 -0
  114. package/hooks/managed-hooks-registry.cjs +1 -0
  115. package/package.json +5 -4
  116. package/scripts/affected-tests-lib.cjs +16 -4
  117. package/scripts/build-hooks.js +7 -0
  118. package/scripts/changeset/new.cjs +17 -3
  119. package/scripts/fix-slash-commands.cjs +15 -3
  120. package/scripts/gen-capability-registry.cjs +373 -49
  121. package/scripts/gen-inventory-manifest.cjs +1 -4
  122. package/scripts/gen-loop-host-contract.cjs +55 -0
  123. package/scripts/issue-version-gate.cjs +140 -0
  124. package/scripts/lint-allow-test-rule-refs.allowlist.json +327 -0
  125. package/scripts/lint-allow-test-rule-refs.cjs +162 -0
  126. package/scripts/lint-test-file-count.allowlist.json +14 -0
  127. package/scripts/mutation-matrix.cjs +108 -7
  128. package/scripts/pr-target-policy.cjs +63 -0
  129. package/scripts/release-tarball-smoke.cjs +7 -1
  130. package/scripts/research-profiles.cjs +5 -5
  131. 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 sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, link['from'] || ''));
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
- check['detail'] = 'Source file not found (from: must be a relative file path; describe components/endpoints in via:)';
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: verified === results.length,
586
+ all_verified: allVerified,
519
587
  verified,
588
+ pending: pendingCount,
520
589
  total: results.length,
521
590
  links: results,
522
- }, raw, verified === results.length ? 'valid' : 'invalid');
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: result['skipped'] || false,
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: !!driftResult['actionRequired'],
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 || Date.now() - lockMtime.getTime() < reapMtimeGuardMs) {
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": "./CLAUDE.md",
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,4 @@
1
+ {
2
+ "items": [],
3
+ "coverage": { "applicable": 0, "resolved": 0, "unresolved": 0, "byVerification": { "test": 0, "judgment": 0 } }
4
+ }
@@ -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
+ }