peaks-loop 4.0.47 → 4.0.49

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 (126) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/agents/karpathy-reviewer.md +11 -10
  5. package/dist/cli/cli-helpers.d.ts +34 -0
  6. package/dist/cli/cli-helpers.js +57 -0
  7. package/dist/cli/commands/code-job-shape-commands.js +8 -0
  8. package/dist/cli/commands/code-runtime-commands.js +48 -8
  9. package/dist/cli/commands/compact-command.js +110 -0
  10. package/dist/cli/commands/config-commands.js +15 -9
  11. package/dist/cli/commands/dashboard-long-run.js +6 -0
  12. package/dist/cli/commands/dispatch-commands.js +11 -1
  13. package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
  14. package/dist/cli/commands/feedback-commands.d.ts +11 -7
  15. package/dist/cli/commands/feedback-commands.js +49 -17
  16. package/dist/cli/commands/final-review-commands.js +12 -0
  17. package/dist/cli/commands/hooks-commands.js +4 -4
  18. package/dist/cli/commands/job-commands.js +8 -0
  19. package/dist/cli/commands/loop-eval-commands.js +31 -0
  20. package/dist/cli/commands/perf-audit-commands.js +2 -0
  21. package/dist/cli/commands/playwright-commands.js +12 -0
  22. package/dist/cli/commands/prd-commands.js +1 -1
  23. package/dist/cli/commands/qa-commands.js +22 -0
  24. package/dist/cli/commands/request-commands.js +8 -0
  25. package/dist/cli/commands/scan-commands.js +1 -1
  26. package/dist/cli/commands/security-audit-commands.js +2 -0
  27. package/dist/cli/commands/slice-integrate-commands.js +22 -0
  28. package/dist/cli/commands/statusline-commands.js +44 -4
  29. package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
  30. package/dist/cli/commands/sub-agent/detached.js +47 -22
  31. package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
  32. package/dist/cli/commands/verdict-aggregate-command.js +95 -13
  33. package/dist/cli/commands/workflow-commands.js +1 -1
  34. package/dist/cli/index.js +5 -45
  35. package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
  36. package/dist/services/artifacts/artifact-prerequisites.js +140 -65
  37. package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
  38. package/dist/services/artifacts/request-artifact-service.js +77 -46
  39. package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
  40. package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
  41. package/dist/services/audit/enforcers/active-skill-resolver.js +14 -1
  42. package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
  43. package/dist/services/audit-independent/perf-audit-service.js +27 -5
  44. package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
  45. package/dist/services/audit-independent/security-audit-service.js +28 -6
  46. package/dist/services/code/auto-compact-lifecycle.d.ts +194 -0
  47. package/dist/services/code/auto-compact-lifecycle.js +229 -11
  48. package/dist/services/code/auto-compact-orchestrator.js +118 -7
  49. package/dist/services/code/compact-event-settle.d.ts +134 -0
  50. package/dist/services/code/compact-event-settle.js +240 -0
  51. package/dist/services/compact-history/compact-history-service.d.ts +14 -0
  52. package/dist/services/compact-statusline/compact-statusline-service.js +56 -22
  53. package/dist/services/config/config-restore.d.ts +12 -1
  54. package/dist/services/config/config-restore.js +35 -4
  55. package/dist/services/config/config-rollback.js +6 -1
  56. package/dist/services/context/auto-compact-types.d.ts +20 -2
  57. package/dist/services/context/harness-context-witness.d.ts +310 -0
  58. package/dist/services/context/harness-context-witness.js +606 -0
  59. package/dist/services/evidence/evidence-generator.js +86 -49
  60. package/dist/services/feedback/feedback-promotion-service.d.ts +137 -14
  61. package/dist/services/feedback/feedback-promotion-service.js +341 -20
  62. package/dist/services/feedback/promotion-artifact-evidence.d.ts +69 -0
  63. package/dist/services/feedback/promotion-artifact-evidence.js +332 -0
  64. package/dist/services/final-review/final-review-service.d.ts +9 -0
  65. package/dist/services/final-review/final-review-service.js +36 -12
  66. package/dist/services/ide/ide-registry.d.ts +19 -0
  67. package/dist/services/ide/ide-registry.js +21 -0
  68. package/dist/services/job/job-progress-store.js +18 -3
  69. package/dist/services/job/job-state-store.js +7 -0
  70. package/dist/services/observability/jsonl-store.d.ts +19 -0
  71. package/dist/services/observability/jsonl-store.js +27 -2
  72. package/dist/services/observability/observability-service.d.ts +10 -3
  73. package/dist/services/observability/observability-service.js +16 -3
  74. package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
  75. package/dist/services/prd/handoff-auto-regen.js +31 -27
  76. package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
  77. package/dist/services/prd/handoff-frontmatter.js +75 -0
  78. package/dist/services/prd/handoff-service.d.ts +41 -2
  79. package/dist/services/prd/handoff-service.js +124 -8
  80. package/dist/services/prd/handoff-types.d.ts +3 -2
  81. package/dist/services/prd/handoff-types.js +3 -2
  82. package/dist/services/qa/qa-business-review-state.js +23 -0
  83. package/dist/services/sc/sc-service.d.ts +8 -0
  84. package/dist/services/sc/sc-service.js +8 -1
  85. package/dist/services/scan/karpathy-service.js +2 -2
  86. package/dist/services/session/getSessionDir.d.ts +33 -0
  87. package/dist/services/session/getSessionDir.js +60 -0
  88. package/dist/services/session/session-checkpoint-service.js +8 -0
  89. package/dist/services/skill/resume-detector.js +29 -11
  90. package/dist/services/skills/hooks-codegate-superpowers.d.ts +6 -0
  91. package/dist/services/skills/hooks-codegate-superpowers.js +61 -2
  92. package/dist/services/skills/hooks-settings-service.js +14 -4
  93. package/dist/services/skills/session-start-hook-constants.d.ts +45 -0
  94. package/dist/services/skills/session-start-hook-constants.js +45 -0
  95. package/dist/services/skills/skill-statusline-service.d.ts +14 -0
  96. package/dist/services/slice/slice-check-service.js +29 -11
  97. package/dist/services/slice/slice-review-state.js +23 -0
  98. package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
  99. package/dist/services/workflow/pipeline-verify-gate-support.js +221 -103
  100. package/dist/services/workflow/pipeline-verify-service.d.ts +1 -1
  101. package/dist/services/workflow/pipeline-verify-service.js +47 -33
  102. package/dist/services/workflow/pipeline-verify-types.d.ts +15 -6
  103. package/dist/services/workspace/claude-settings-template.d.ts +56 -8
  104. package/dist/services/workspace/claude-settings-template.js +98 -20
  105. package/dist/services/workspace/workspace-claude-settings-materializer.js +78 -7
  106. package/dist/shared/runtime-root.d.ts +73 -0
  107. package/dist/shared/runtime-root.js +77 -0
  108. package/package.json +6 -6
  109. package/skills/bee/peaks-prd/SKILL.md +7 -5
  110. package/skills/bee/peaks-qa/SKILL.md +5 -5
  111. package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
  112. package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
  113. package/skills/bee/peaks-rd/SKILL.md +8 -6
  114. package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
  115. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
  116. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
  117. package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
  118. package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
  119. package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
  120. package/skills/peaks-code/SKILL.md +1 -1
  121. package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
  122. package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
  123. package/skills/peaks-code/references/resume-detection.md +13 -7
  124. package/skills/peaks-code/references/runbook.md +3 -2
  125. package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
  126. package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * File budget: ≤ 400 lines (rid-006 split).
10
10
  */
11
- import type { RequestType } from '../artifacts/artifact-prerequisites.js';
11
+ import { type ArtifactPrerequisite, type RequestType } from '../artifacts/artifact-prerequisites.js';
12
+ import type { RequestArtifactRole, RequestArtifactState } from '../artifacts/request-artifact-service.js';
12
13
  import type { PipelineGate } from './pipeline-verify-types.js';
13
14
  export declare function extractState(markdown: string): string;
14
15
  /**
@@ -24,6 +25,38 @@ export declare function findRequestFile(projectRoot: string, role: string, rid:
24
25
  content: string;
25
26
  sessionId: string;
26
27
  } | null>;
28
+ /**
29
+ * Where the CURRENT contract puts one gate's evidence.
30
+ *
31
+ * The contract is the `artifact-prerequisites.ts` table — the table
32
+ * `peaks request transition` enforces and the one the peaks-qa prose
33
+ * contract mirrors (`skills/bee/peaks-qa/references/qa-transition-gates.md`).
34
+ * `probeName` is the location this checker probes today. The contract's
35
+ * `relativePath` is returned first, then its accepted historical locations in
36
+ * the table's own declared order — `legacyRelativePath`, then
37
+ * `legacyRelativePaths` (the same composition `artifact-prerequisites.ts`' own
38
+ * resolver performs, so a hit here and a hit there land on the same file).
39
+ *
40
+ * Returns `null` when the contract carries no prerequisite for that name at
41
+ * all — the artifact was retired and no gate may fail on it.
42
+ *
43
+ * This lookup exists because hard-coding the paths here is what let
44
+ * `verify-pipeline` fall a minor release behind the gate: it kept demanding
45
+ * `rd/security-review.md` as mandatory after the table had demoted that file
46
+ * to a `legacyRelativePath` fallback of `audit/security.md`, and it kept
47
+ * demanding `qa/security-findings-<rid>.md` / `qa/performance-findings-<rid>.md`
48
+ * after the v2.11.0 D1/D4 trim dropped them from `qa:verdict-issued`
49
+ * altogether (rid 2026-09-14-verify-pipeline-contract-drift).
50
+ */
51
+ /**
52
+ * The prerequisite the contract carries for `probeName`, or null when it
53
+ * carries none. `contractEvidencePaths` is this object's path projection; the
54
+ * BODY contract (`mustContain` / `mustContainAny` / `headingMustContain`) rides
55
+ * on the same object, which is why `contractBodyViolations` below consults it
56
+ * instead of probing for the file's existence alone.
57
+ */
58
+ export declare function contractPrerequisite(role: RequestArtifactRole, state: RequestArtifactState, requestType: RequestType, probeName: string): ArtifactPrerequisite | null;
59
+ export declare function contractEvidencePaths(role: RequestArtifactRole, state: RequestArtifactState, requestType: RequestType, probeName: string): string[] | null;
27
60
  export declare function rdGatesForType(requestType: RequestType): PipelineGate[];
28
61
  export declare function qaGatesForType(requestType: RequestType): PipelineGate[];
29
62
  export declare const RD_QA_HANDOFF_STATES: Set<string>;
@@ -33,15 +66,19 @@ export interface CanonicalPathTracker {
33
66
  anyEvidenceResolved: boolean;
34
67
  allResolvedPathsCanonical: boolean;
35
68
  }
36
- /** Resolve RD evidence files (tech-doc / bug-analysis / code-review / security-review)
69
+ /** Resolve RD evidence files (prd-handoff / bug-analysis / code-review / security-review / perf-baseline)
37
70
  * by probing canonical + legacy paths for each gate. Mutates the gate.passed /
38
71
  * gate.detail fields in place and pushes violations / nextActions. Returns the
39
72
  * updated tracker. Verbatim-move from `pipeline-verify-service.ts`. */
40
- export declare function resolveRdEvidencePaths(gates: PipelineGate[], rdEvidenceDir: string, projectRoot: string, violations: string[], nextActions: string[], tracker: CanonicalPathTracker): CanonicalPathTracker;
41
- /** Resolve QA evidence files (test-cases / test-report / security-findings /
42
- * performance-findings) by probing canonical + legacy paths for each gate.
43
- * For security/perf findings, delegates to the artifact-paths resolver.
44
- * Mutates the gate.passed / gate.detail fields in place and pushes
45
- * violations / nextActions. Returns the updated tracker. Verbatim-move
46
- * from `pipeline-verify-service.ts`. */
47
- export declare function resolveQaEvidencePaths(gates: PipelineGate[], projectRoot: string, rdEvidenceDir: string, changeIdForResolver: string, rid: string, violations: string[], nextActions: string[], tracker: CanonicalPathTracker): CanonicalPathTracker;
73
+ export declare function resolveRdEvidencePaths(gates: PipelineGate[], rdEvidenceDir: string, projectRoot: string, rid: string, requestType: RequestType, violations: string[], nextActions: string[], tracker: CanonicalPathTracker): CanonicalPathTracker;
74
+ /** Resolve QA evidence files (test-cases / test-report) by probing canonical
75
+ * + legacy paths for each gate. Mutates the gate.passed / gate.detail fields
76
+ * in place and pushes violations / nextActions. Returns the updated tracker.
77
+ * Verbatim-move from `pipeline-verify-service.ts`.
78
+ *
79
+ * The `security-findings` / `performance-findings` branches that used to live
80
+ * here are gone: those artifacts are not in any `qa:verdict-issued` table
81
+ * (v2.11.0 D1/D4), and the evidence they stood for is resolved on the RD side
82
+ * at the contract's own paths (`audit/security-<rid>.md`,
83
+ * `audit/perf-<rid>.md`). */
84
+ export declare function resolveQaEvidencePaths(gates: PipelineGate[], projectRoot: string, rdEvidenceDir: string, requestType: RequestType, rid: string, violations: string[], nextActions: string[], tracker: CanonicalPathTracker): CanonicalPathTracker;
@@ -8,17 +8,13 @@
8
8
  *
9
9
  * File budget: ≤ 400 lines (rid-006 split).
10
10
  */
11
- import { existsSync } from 'node:fs';
11
+ import { existsSync, readFileSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
+ import { getPrerequisitesFor, prerequisiteBodyViolations } from '../artifacts/artifact-prerequisites.js';
13
14
  import { showRequestArtifact } from '../artifacts/request-artifact-service.js';
14
- import { resolveSecurityFindingsPath, resolvePerformanceFindingsPath } from './artifact-paths.js';
15
+ import { readArtifactState } from '../artifacts/request-artifact-state-helpers.js';
15
16
  export function extractState(markdown) {
16
- for (const rawLine of markdown.split(/\r?\n/)) {
17
- const match = /^-\s*state:\s*(.+?)\s*$/.exec(rawLine.trim());
18
- if (match?.[1])
19
- return match[1];
20
- }
21
- return 'unknown';
17
+ return readArtifactState(markdown) ?? 'unknown';
22
18
  }
23
19
  /**
24
20
  * As of slice 2026-06-05-change-id-as-unit-of-work, the file's durable
@@ -32,24 +28,102 @@ export async function findRequestFile(projectRoot, role, rid) {
32
28
  const artifact = await showRequestArtifact({ projectRoot, role: role, requestId: rid });
33
29
  if (artifact === null)
34
30
  return null;
35
- // Slice 2026-06-28-code-mode-bypass-fix (defect #3): the legacy
36
- // `showRequestArtifact` returns the FULL SCOPE (`_runtime/<sid>`)
37
- // as `sessionId`, not just the trailing id segment. The canonical
38
- // evidence lookup needs only the bare id (`.peaks/_runtime/change/<id>/`).
39
- // When the scope starts with `_runtime/`, strip that prefix so the
40
- // path resolver builds the right canonical location.
41
- let sessionId = artifact.sessionId;
42
- if (sessionId.startsWith('_runtime/') || sessionId.startsWith('_runtime\\')) {
43
- sessionId = sessionId.replace(/^_runtime[\\/]/, '');
31
+ // Slice 2026-06-28-code-mode-bypass-fix (defect #3) used to strip a
32
+ // `_runtime/` prefix here, because `showRequestArtifact` then returned the
33
+ // FULL SCOPE (`_runtime/<sid>`) as `sessionId`. Repair R5 removed that
34
+ // round-trip at its source: `readSummary` now builds the summary from the
35
+ // already-resolved directory and the bare id, so `sessionId` is the bare id
36
+ // and the prefix strip could never fire. It was removed by repair R7 rather
37
+ // than left in place as dead code with a comment asserting a behaviour its
38
+ // callee no longer has — an artifact claiming something the code does not do
39
+ // is the defect class this line of work exists to remove.
40
+ return { path: artifact.path, content: artifact.content, sessionId: artifact.sessionId };
41
+ }
42
+ /**
43
+ * Where the CURRENT contract puts one gate's evidence.
44
+ *
45
+ * The contract is the `artifact-prerequisites.ts` table — the table
46
+ * `peaks request transition` enforces and the one the peaks-qa prose
47
+ * contract mirrors (`skills/bee/peaks-qa/references/qa-transition-gates.md`).
48
+ * `probeName` is the location this checker probes today. The contract's
49
+ * `relativePath` is returned first, then its accepted historical locations in
50
+ * the table's own declared order — `legacyRelativePath`, then
51
+ * `legacyRelativePaths` (the same composition `artifact-prerequisites.ts`' own
52
+ * resolver performs, so a hit here and a hit there land on the same file).
53
+ *
54
+ * Returns `null` when the contract carries no prerequisite for that name at
55
+ * all — the artifact was retired and no gate may fail on it.
56
+ *
57
+ * This lookup exists because hard-coding the paths here is what let
58
+ * `verify-pipeline` fall a minor release behind the gate: it kept demanding
59
+ * `rd/security-review.md` as mandatory after the table had demoted that file
60
+ * to a `legacyRelativePath` fallback of `audit/security.md`, and it kept
61
+ * demanding `qa/security-findings-<rid>.md` / `qa/performance-findings-<rid>.md`
62
+ * after the v2.11.0 D1/D4 trim dropped them from `qa:verdict-issued`
63
+ * altogether (rid 2026-09-14-verify-pipeline-contract-drift).
64
+ */
65
+ /**
66
+ * The prerequisite the contract carries for `probeName`, or null when it
67
+ * carries none. `contractEvidencePaths` is this object's path projection; the
68
+ * BODY contract (`mustContain` / `mustContainAny` / `headingMustContain`) rides
69
+ * on the same object, which is why `contractBodyViolations` below consults it
70
+ * instead of probing for the file's existence alone.
71
+ */
72
+ export function contractPrerequisite(role, state, requestType, probeName) {
73
+ for (const prereq of getPrerequisitesFor(role, state, requestType)) {
74
+ const legacyPaths = [
75
+ ...(prereq.legacyRelativePath !== undefined ? [prereq.legacyRelativePath] : []),
76
+ ...(prereq.legacyRelativePaths ?? [])
77
+ ];
78
+ if (prereq.relativePath !== probeName && !legacyPaths.includes(probeName))
79
+ continue;
80
+ return prereq;
44
81
  }
45
- return { path: artifact.path, content: artifact.content, sessionId };
82
+ return null;
83
+ }
84
+ export function contractEvidencePaths(role, state, requestType, probeName) {
85
+ const prereq = contractPrerequisite(role, state, requestType, probeName);
86
+ if (prereq === null)
87
+ return null;
88
+ const legacyPaths = [
89
+ ...(prereq.legacyRelativePath !== undefined ? [prereq.legacyRelativePath] : []),
90
+ ...(prereq.legacyRelativePaths ?? [])
91
+ ];
92
+ return [prereq.relativePath, ...legacyPaths.filter((legacy) => legacy !== prereq.relativePath)];
93
+ }
94
+ /**
95
+ * The contract's BODY checks for the prerequisite named by `probeName`, applied
96
+ * to the file that resolved. Empty when the name matches nothing in the table or
97
+ * the table pins no body markers for it.
98
+ *
99
+ * `existsSync` alone made this checker laxer than the contract it claims to
100
+ * derive from: it reported `prd-handoff passed = true` for a handoff carrying
101
+ * `schemaVersion: 1` and no `sha256:` line — the exact file
102
+ * `AUDIT_REQUIRES_HANDOFF` refuses at `rd:qa-handoff`. Deriving the *path* from
103
+ * the table while dropping the table's *body* contract is the drift this module
104
+ * exists to remove, so the check goes through the table's own implementation
105
+ * (`prerequisiteBodyViolations`) rather than a second copy of the markers.
106
+ */
107
+ function contractBodyViolations(role, state, requestType, probeName, absolutePath) {
108
+ const prerequisite = contractPrerequisite(role, state, requestType, probeName);
109
+ if (prerequisite === null)
110
+ return [];
111
+ return prerequisiteBodyViolations(prerequisite, readFileSync(absolutePath, 'utf8'));
46
112
  }
47
113
  export function rdGatesForType(requestType) {
48
114
  const gates = [
49
115
  { name: 'rd-request-exists', description: 'RD request artifact created', passed: false, detail: '' }
50
116
  ];
51
- if (requestType === 'feature' || requestType === 'refactor') {
52
- gates.push({ name: 'tech-doc', description: 'Technical design doc', passed: false, detail: '' });
117
+ // This slot used to pin a `tech-doc` gate to `rd/tech-doc.md`, an artifact
118
+ // v2.11.0 Group A retired and the table no longer carries — so the gate
119
+ // failed trees `peaks request transition` accepts (QA repair cycle 1). The
120
+ // design / scope record the contract DOES name at `rd:qa-handoff` is
121
+ // `prd/handoff.md`, so that is the gate: table-derived, like the others.
122
+ // (`rd/tech-doc.md` still has two non-gate readers — final-review's evidence
123
+ // budget and the resume detector. Neither is served by failing a compliant
124
+ // slice.)
125
+ if (contractEvidencePaths('rd', 'qa-handoff', requestType, 'prd/handoff.md') !== null) {
126
+ gates.push({ name: 'prd-handoff', description: 'PRD handoff capsule (approved scope + non-goals)', passed: false, detail: '' });
53
127
  }
54
128
  if (requestType === 'bugfix') {
55
129
  gates.push({ name: 'bug-analysis', description: 'Bug root-cause analysis', passed: false, detail: '' });
@@ -60,6 +134,17 @@ export function rdGatesForType(requestType) {
60
134
  if (requestType === 'feature' || requestType === 'refactor' || requestType === 'bugfix' || requestType === 'config') {
61
135
  gates.push({ name: 'security-review', description: 'Security review evidence', passed: false, detail: '' });
62
136
  }
137
+ // The perf evidence the contract requires at `rd:qa-handoff` is `AUDIT_PERF`
138
+ // — `audit/perf-<rid>.md`, with `audit/perf.md` and `rd/perf-baseline.md` as
139
+ // its two declared legacy tiers (rid-scoped since slice
140
+ // `2026-09-14-audit-artifact-rid-scoping`). This gate used to live on the QA
141
+ // side as `performance-findings`, checking `qa/performance-findings-<rid>.md`
142
+ // — a path the v2.11.0 D1/D4 trim dropped. Relocating the gate (rather than
143
+ // deleting it) is what keeps AC3's control meaningful: perf evidence that is
144
+ // genuinely absent must still fail.
145
+ if (contractEvidencePaths('rd', 'qa-handoff', requestType, 'rd/perf-baseline.md') !== null) {
146
+ gates.push({ name: 'perf-baseline', description: 'Performance audit evidence', passed: false, detail: '' });
147
+ }
63
148
  return gates;
64
149
  }
65
150
  export function qaGatesForType(requestType) {
@@ -70,124 +155,157 @@ export function qaGatesForType(requestType) {
70
155
  gates.push({ name: 'test-cases', description: 'QA test cases', passed: false, detail: '' });
71
156
  gates.push({ name: 'test-report', description: 'QA test report with execution results', passed: false, detail: '' });
72
157
  }
73
- if (requestType === 'feature' || requestType === 'refactor' || requestType === 'bugfix' || requestType === 'config') {
74
- gates.push({ name: 'security-findings', description: 'QA security findings', passed: false, detail: '' });
75
- }
76
- if (requestType === 'feature' || requestType === 'refactor') {
77
- gates.push({ name: 'performance-findings', description: 'QA performance findings', passed: false, detail: '' });
78
- }
158
+ // The `security-findings` / `performance-findings` gates were removed here
159
+ // (rid 2026-09-14-verify-pipeline-contract-drift). peaks-qa does not own
160
+ // security review or performance review — `peaks-qa/SKILL.md` says so, and
161
+ // the v2.11.0 D1/D4 trim dropped both from every `qa:verdict-issued` table.
162
+ // The evidence they used to demand is now checked on the RD side, at the
163
+ // paths the contract actually names (`audit/security-<rid>.md`,
164
+ // `audit/perf-<rid>.md`; rid-scoped since slice
165
+ // `2026-09-14-audit-artifact-rid-scoping`, with `audit/security.md` /
166
+ // `audit/perf.md` and `rd/security-review.md` / `rd/perf-baseline.md` as the
167
+ // declared legacy tiers behind them).
79
168
  return gates;
80
169
  }
81
170
  export const RD_QA_HANDOFF_STATES = new Set(['qa-handoff', 'handed-off', 'implemented']);
82
171
  export const QA_COMPLETE_STATES = new Set(['verdict-issued']);
83
- /** Resolve RD evidence files (tech-doc / bug-analysis / code-review / security-review)
172
+ /** Resolve RD evidence files (prd-handoff / bug-analysis / code-review / security-review / perf-baseline)
84
173
  * by probing canonical + legacy paths for each gate. Mutates the gate.passed /
85
174
  * gate.detail fields in place and pushes violations / nextActions. Returns the
86
175
  * updated tracker. Verbatim-move from `pipeline-verify-service.ts`. */
87
- export function resolveRdEvidencePaths(gates, rdEvidenceDir, projectRoot, violations, nextActions, tracker) {
88
- const RD_EVIDENCE_FILE = {
89
- 'tech-doc': 'tech-doc.md',
90
- 'bug-analysis': 'bug-analysis.md',
91
- 'code-review': 'code-review.md',
92
- 'security-review': 'security-review.md'
176
+ export function resolveRdEvidencePaths(gates, rdEvidenceDir, projectRoot, rid, requestType, violations, nextActions, tracker) {
177
+ // The path each gate is about. The contract is consulted first — see
178
+ // `contractEvidencePaths`; `rdGatesForType` only builds a gate when the
179
+ // contract carries its prerequisite, so this is the name the gate stands
180
+ // for, not a second path table.
181
+ const RD_EVIDENCE_PROBE = {
182
+ 'prd-handoff': 'prd/handoff.md',
183
+ 'bug-analysis': 'rd/bug-analysis.md',
184
+ 'code-review': 'rd/code-review.md',
185
+ 'security-review': 'rd/security-review.md',
186
+ 'perf-baseline': 'rd/perf-baseline.md'
93
187
  };
94
188
  let anyEvidenceResolved = tracker.anyEvidenceResolved;
95
189
  let allResolvedPathsCanonical = tracker.allResolvedPathsCanonical;
96
190
  for (const gate of gates.slice(1)) {
97
- const fileName = RD_EVIDENCE_FILE[gate.name];
98
- const canonicalPath = join(projectRoot, '.peaks', '_runtime', rdEvidenceDir, 'rd', fileName);
99
- const legacyMisplacedPath = join(projectRoot, '.peaks', rdEvidenceDir, 'rd', fileName);
100
- const legacyChangeAxisPath = join(projectRoot, '.peaks', '_runtime', 'change', rdEvidenceDir, 'rd', fileName);
101
- let resolvedPath = null;
102
- let usedLegacy = false;
103
- for (const candidate of [canonicalPath, legacyMisplacedPath, legacyChangeAxisPath]) {
104
- if (existsSync(candidate)) {
105
- resolvedPath = candidate;
106
- usedLegacy = candidate !== canonicalPath;
107
- break;
191
+ const probeName = RD_EVIDENCE_PROBE[gate.name];
192
+ const relativePaths = (contractEvidencePaths('rd', 'qa-handoff', requestType, probeName) ?? [probeName])
193
+ .map((relative) => relative.replace('<rid>', rid));
194
+ const primaryRelativePath = relativePaths[0];
195
+ const canonicalPath = join(projectRoot, '.peaks', '_runtime', rdEvidenceDir, primaryRelativePath);
196
+ // Two independent axes, kept apart because they mean different things:
197
+ // - `legacyRoot` — the file sits under a pre-F3 session home
198
+ // (`.peaks/<sid>/…` or `.peaks/_runtime/change/<sid>/…`). That is a
199
+ // misplaced write, and it is the DEPRECATION violation's subject.
200
+ // - `legacyForm` — the file sits under one of the contract's declared
201
+ // `legacyRelativePaths`. The contract accepts those on purpose ("a
202
+ // legacy hit must keep the gate open, or existing sessions would fail
203
+ // the transition on upgrade"), so this is reported through
204
+ // `acceptedForm` and is *not* a violation.
205
+ const candidates = [];
206
+ for (const relative of relativePaths) {
207
+ const legacyForm = relative !== primaryRelativePath;
208
+ for (const [root, legacyRoot] of [
209
+ [join(projectRoot, '.peaks', '_runtime', rdEvidenceDir), false],
210
+ [join(projectRoot, '.peaks', rdEvidenceDir), true],
211
+ [join(projectRoot, '.peaks', '_runtime', 'change', rdEvidenceDir), true]
212
+ ]) {
213
+ candidates.push({ path: join(root, relative), legacyRoot, legacyForm });
108
214
  }
109
215
  }
110
- if (resolvedPath !== null) {
216
+ const hit = candidates.find((candidate) => existsSync(candidate.path));
217
+ if (hit !== undefined) {
111
218
  anyEvidenceResolved = true;
112
- if (usedLegacy)
219
+ if (hit.legacyRoot)
113
220
  allResolvedPathsCanonical = false;
114
- gate.passed = true;
115
- gate.detail = resolvedPath + (usedLegacy ? ' [DEPRECATION_LEGACY_PATH_USED]' : '');
116
- if (usedLegacy) {
117
- violations.push(`DEPRECATION_LEGACY_PATH_USED: ${resolvedPath} — move the file into .peaks/_runtime/${rdEvidenceDir}/rd/ (the canonical location) so subsequent runs resolve on the canonical path. The legacy \`peaks workspace migrate-change-scope\` helper was removed in v2.19.0; use \`peaks workspace migrate\` to relocate misplaced content.`);
221
+ // Existing is not the contract — the file must also satisfy the body
222
+ // markers the table pins for this prereq (see `contractBodyViolations`).
223
+ const bodyViolations = contractBodyViolations('rd', 'qa-handoff', requestType, probeName, hit.path);
224
+ if (bodyViolations.length > 0) {
225
+ gate.passed = false;
226
+ gate.detail = `${hit.path} — ${bodyViolations.join('; ')}`;
227
+ violations.push(`RD evidence does not satisfy the contract: ${gate.description} (${primaryRelativePath}) — ${bodyViolations.join('; ')}`);
228
+ nextActions.push(`Fix .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath} to satisfy the contract: ${bodyViolations.join('; ')}`);
229
+ }
230
+ else {
231
+ gate.passed = true;
232
+ gate.detail = hit.path
233
+ + (hit.legacyRoot ? ' [DEPRECATION_LEGACY_PATH_USED]' : '')
234
+ + (hit.legacyForm ? ' [LEGACY_EVIDENCE_PATH]' : '');
235
+ }
236
+ if (hit.legacyRoot) {
237
+ violations.push(`DEPRECATION_LEGACY_PATH_USED: ${hit.path} — move the file into .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath} (the canonical location) so subsequent runs resolve on the canonical path. The legacy \`peaks workspace migrate-change-scope\` helper was removed in v2.19.0; use \`peaks workspace migrate\` to relocate misplaced content.`);
118
238
  }
119
239
  }
120
240
  else {
121
241
  gate.detail = `missing: ${canonicalPath}`;
122
- violations.push(`RD evidence missing: ${gate.description} (${fileName})`);
123
- nextActions.push(`Create .peaks/_runtime/${rdEvidenceDir}/rd/${fileName}`);
242
+ violations.push(`RD evidence missing: ${gate.description} (${primaryRelativePath})`);
243
+ nextActions.push(`Create .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath}`);
124
244
  }
125
245
  }
126
246
  return { anyEvidenceResolved, allResolvedPathsCanonical };
127
247
  }
128
- /** Resolve QA evidence files (test-cases / test-report / security-findings /
129
- * performance-findings) by probing canonical + legacy paths for each gate.
130
- * For security/perf findings, delegates to the artifact-paths resolver.
131
- * Mutates the gate.passed / gate.detail fields in place and pushes
132
- * violations / nextActions. Returns the updated tracker. Verbatim-move
133
- * from `pipeline-verify-service.ts`. */
134
- export function resolveQaEvidencePaths(gates, projectRoot, rdEvidenceDir, changeIdForResolver, rid, violations, nextActions, tracker) {
135
- const QA_EVIDENCE_FILE = {
136
- 'test-cases': `test-cases/${rid}.md`,
137
- 'test-report': `test-reports/${rid}.md`,
138
- 'security-findings': '',
139
- 'performance-findings': ''
248
+ /** Resolve QA evidence files (test-cases / test-report) by probing canonical
249
+ * + legacy paths for each gate. Mutates the gate.passed / gate.detail fields
250
+ * in place and pushes violations / nextActions. Returns the updated tracker.
251
+ * Verbatim-move from `pipeline-verify-service.ts`.
252
+ *
253
+ * The `security-findings` / `performance-findings` branches that used to live
254
+ * here are gone: those artifacts are not in any `qa:verdict-issued` table
255
+ * (v2.11.0 D1/D4), and the evidence they stood for is resolved on the RD side
256
+ * at the contract's own paths (`audit/security-<rid>.md`,
257
+ * `audit/perf-<rid>.md`). */
258
+ export function resolveQaEvidencePaths(gates, projectRoot, rdEvidenceDir, requestType, rid, violations, nextActions, tracker) {
259
+ const QA_EVIDENCE_PROBE = {
260
+ 'test-cases': 'qa/test-cases/<rid>.md',
261
+ 'test-report': 'qa/test-reports/<rid>.md'
140
262
  };
141
263
  let anyEvidenceResolved = tracker.anyEvidenceResolved;
142
264
  let allResolvedPathsCanonical = tracker.allResolvedPathsCanonical;
143
265
  for (const gate of gates.slice(1)) {
144
- if (gate.name === 'security-findings' || gate.name === 'performance-findings') {
145
- const resolver = gate.name === 'security-findings' ? resolveSecurityFindingsPath : resolvePerformanceFindingsPath;
146
- const resolved = resolver({ projectRoot, sessionId: changeIdForResolver, rid });
147
- if (existsSync(resolved.path)) {
148
- anyEvidenceResolved = true;
149
- if (resolved.form === 'legacy')
150
- allResolvedPathsCanonical = false;
151
- gate.passed = true;
152
- gate.detail = resolved.path;
153
- if (resolved.form === 'legacy') {
154
- violations.push(`QA evidence accepted in legacy form (will be rejected after next minor release): ${resolved.path} — re-run peaks workflow plan refresh to migrate`);
155
- }
156
- }
157
- else {
158
- gate.detail = `missing: ${resolved.path}`;
159
- violations.push(`QA evidence missing: ${gate.description} (${resolved.path})`);
160
- nextActions.push(`Create ${resolved.path} (or use the legacy non-suffixed form during the 1-minor-release back-compat window)`);
161
- }
162
- continue;
163
- }
164
- const fileName = QA_EVIDENCE_FILE[gate.name];
165
- const canonicalQaPath = join(projectRoot, '.peaks', '_runtime', rdEvidenceDir, 'qa', fileName);
166
- const legacyMisplacedQaPath = join(projectRoot, '.peaks', rdEvidenceDir, 'qa', fileName);
167
- const legacyChangeAxisQaPath = join(projectRoot, '.peaks', '_runtime', 'change', rdEvidenceDir, 'qa', fileName);
168
- let resolvedQaPath = null;
169
- let usedLegacyQa = false;
170
- for (const candidate of [canonicalQaPath, legacyMisplacedQaPath, legacyChangeAxisQaPath]) {
171
- if (existsSync(candidate)) {
172
- resolvedQaPath = candidate;
173
- usedLegacyQa = candidate !== canonicalQaPath;
174
- break;
266
+ const probeName = QA_EVIDENCE_PROBE[gate.name];
267
+ const relativePaths = (contractEvidencePaths('qa', 'verdict-issued', requestType, probeName) ?? [probeName])
268
+ .map((relative) => relative.replace('<rid>', rid));
269
+ const primaryRelativePath = relativePaths[0];
270
+ const canonicalQaPath = join(projectRoot, '.peaks', '_runtime', rdEvidenceDir, primaryRelativePath);
271
+ const candidates = [];
272
+ for (const relative of relativePaths) {
273
+ for (const [root, legacyRoot] of [
274
+ [join(projectRoot, '.peaks', '_runtime', rdEvidenceDir), false],
275
+ [join(projectRoot, '.peaks', rdEvidenceDir), true],
276
+ [join(projectRoot, '.peaks', '_runtime', 'change', rdEvidenceDir), true]
277
+ ]) {
278
+ candidates.push({ path: join(root, relative), legacyRoot });
175
279
  }
176
280
  }
177
- if (resolvedQaPath !== null) {
281
+ const hit = candidates.find((candidate) => existsSync(candidate.path));
282
+ if (hit !== undefined) {
178
283
  anyEvidenceResolved = true;
179
- if (usedLegacyQa)
284
+ if (hit.legacyRoot)
180
285
  allResolvedPathsCanonical = false;
181
- gate.passed = true;
182
- gate.detail = resolvedQaPath + (usedLegacyQa ? ' [DEPRECATION_LEGACY_PATH_USED]' : '');
183
- if (usedLegacyQa) {
184
- violations.push(`DEPRECATION_LEGACY_PATH_USED: ${resolvedQaPath} — move the file into .peaks/_runtime/${rdEvidenceDir}/qa/ (the canonical location) so subsequent runs resolve on the canonical path. The legacy \`peaks workspace migrate-change-scope\` helper was removed in v2.19.0; use \`peaks workspace migrate\` to relocate misplaced content.`);
286
+ // Same contract check as the RD side: the `test-report` / `test-cases`
287
+ // prereqs pin `## Test execution` / `## Test cases` / `test(`, and a
288
+ // checker that only probed existence reported green on files the
289
+ // `qa:verdict-issued` gate rejects.
290
+ const bodyViolations = contractBodyViolations('qa', 'verdict-issued', requestType, probeName, hit.path);
291
+ if (bodyViolations.length > 0) {
292
+ gate.passed = false;
293
+ gate.detail = `${hit.path} — ${bodyViolations.join('; ')}`;
294
+ violations.push(`QA evidence does not satisfy the contract: ${gate.description} (${primaryRelativePath}) — ${bodyViolations.join('; ')}`);
295
+ nextActions.push(`Fix .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath} to satisfy the contract: ${bodyViolations.join('; ')}`);
296
+ }
297
+ else {
298
+ gate.passed = true;
299
+ gate.detail = hit.path + (hit.legacyRoot ? ' [DEPRECATION_LEGACY_PATH_USED]' : '');
300
+ }
301
+ if (hit.legacyRoot) {
302
+ violations.push(`DEPRECATION_LEGACY_PATH_USED: ${hit.path} — move the file into .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath} (the canonical location) so subsequent runs resolve on the canonical path. The legacy \`peaks workspace migrate-change-scope\` helper was removed in v2.19.0; use \`peaks workspace migrate\` to relocate misplaced content.`);
185
303
  }
186
304
  }
187
305
  else {
188
306
  gate.detail = `missing: ${canonicalQaPath}`;
189
- violations.push(`QA evidence missing: ${gate.description} (${fileName})`);
190
- nextActions.push(`Create .peaks/_runtime/${rdEvidenceDir}/qa/${fileName}`);
307
+ violations.push(`QA evidence missing: ${gate.description} (${primaryRelativePath})`);
308
+ nextActions.push(`Create .peaks/_runtime/${rdEvidenceDir}/${primaryRelativePath}`);
191
309
  }
192
310
  }
193
311
  return { anyEvidenceResolved, allResolvedPathsCanonical };
@@ -5,7 +5,7 @@
5
5
  * returns a structured `PipelineVerification` envelope. Type
6
6
  * declarations live in `pipeline-verify-types.ts`; private gate
7
7
  * helpers (`rdGatesForType`, `qaGatesForType`, `extractState`,
8
- * `findRequestFile`, the `_runtime/` prefix strip, the RD/QA handoff
8
+ * `findRequestFile`, the RD/QA handoff
9
9
  * state sets, and the RD/QA evidence path probes) live in
10
10
  * `pipeline-verify-gate-support.ts`. The re-export shim at the
11
11
  * bottom preserves the original public surface so existing import