@planu/cli 5.3.26 → 5.3.27

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 (31) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/engine/handoff-artifacts/io.js +1 -0
  3. package/dist/engine/handoff-artifacts/schemas.d.ts +46 -0
  4. package/dist/engine/handoff-artifacts/schemas.js +15 -0
  5. package/dist/engine/lifecycle-reconciliation.js +6 -5
  6. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  7. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +4 -4
  8. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  9. package/dist/engine/planu-core.darwin-x64.node.sbom.json +4 -4
  10. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  11. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +4 -4
  12. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  13. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +4 -4
  14. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  15. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +4 -4
  16. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  17. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +4 -4
  18. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  19. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +4 -4
  20. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  21. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +4 -4
  22. package/dist/engine/scope-boundaries/contradiction-checker.js +1 -1
  23. package/dist/engine/validator/validation-report-writer.d.ts +1 -0
  24. package/dist/engine/validator/validation-report-writer.js +3 -2
  25. package/dist/tools/update-status/dod-gates.d.ts +1 -0
  26. package/dist/tools/update-status/dod-gates.js +123 -4
  27. package/dist/types/handoff-artifacts.d.ts +17 -1
  28. package/dist/types/transition-log.d.ts +1 -1
  29. package/package.json +9 -9
  30. package/planu-native.json +1 -1
  31. package/planu-plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [5.3.27] - 2026-08-19
2
+
3
+ ### Bug Fixes
4
+ - fix(spec-1575): require clause to assert forbidden action for outOfScope contradiction
5
+ - fix(spec-1539): require real independent implementation-review before done
6
+
7
+ ### Chores
8
+ - chore(spec-1539-1575): close done ceremonies, reconcile specs, file SPEC-1576
9
+
10
+
1
11
  ## [5.3.26] - 2026-08-19
2
12
 
3
13
  ### Bug Fixes
@@ -32,6 +32,7 @@ function eventTypeForKind(kind) {
32
32
  review_feedback: 'handoff.review_feedback',
33
33
  'implementation-report': 'handoff.implementation-report',
34
34
  'validation-report': 'handoff.validation-report',
35
+ implementation_review: 'handoff.implementation_review',
35
36
  };
36
37
  return map[kind];
37
38
  }
@@ -53,6 +53,29 @@ export declare const ReviewFeedbackV1Schema: z.ZodObject<{
53
53
  body: z.ZodString;
54
54
  reviewedAt: z.ZodISODateTime;
55
55
  }, z.core.$strip>;
56
+ export declare const ImplementationReviewV1Schema: z.ZodObject<{
57
+ schema_version: z.ZodString;
58
+ specId: z.ZodString;
59
+ verdict: z.ZodEnum<{
60
+ approved: "approved";
61
+ "changes-requested": "changes-requested";
62
+ }>;
63
+ reviewer: z.ZodObject<{
64
+ kind: z.ZodEnum<{
65
+ human: "human";
66
+ "implementation-review-agent": "implementation-review-agent";
67
+ }>;
68
+ agent: z.ZodString;
69
+ verdict: z.ZodEnum<{
70
+ approved: "approved";
71
+ "changes-requested": "changes-requested";
72
+ }>;
73
+ }, z.core.$strip>;
74
+ blockers: z.ZodArray<z.ZodString>;
75
+ suggestions: z.ZodArray<z.ZodString>;
76
+ body: z.ZodString;
77
+ reviewedAt: z.ZodISODateTime;
78
+ }, z.core.$strip>;
56
79
  export declare const ImplementationReportV1Schema: z.ZodObject<{
57
80
  schema_version: z.ZodString;
58
81
  specId: z.ZodString;
@@ -314,6 +337,29 @@ export declare const ARTIFACT_SCHEMAS: {
314
337
  score: z.ZodOptional<z.ZodNumber>;
315
338
  completedAt: z.ZodISODateTime;
316
339
  }, z.core.$strip>;
340
+ readonly implementation_review: z.ZodObject<{
341
+ schema_version: z.ZodString;
342
+ specId: z.ZodString;
343
+ verdict: z.ZodEnum<{
344
+ approved: "approved";
345
+ "changes-requested": "changes-requested";
346
+ }>;
347
+ reviewer: z.ZodObject<{
348
+ kind: z.ZodEnum<{
349
+ human: "human";
350
+ "implementation-review-agent": "implementation-review-agent";
351
+ }>;
352
+ agent: z.ZodString;
353
+ verdict: z.ZodEnum<{
354
+ approved: "approved";
355
+ "changes-requested": "changes-requested";
356
+ }>;
357
+ }, z.core.$strip>;
358
+ blockers: z.ZodArray<z.ZodString>;
359
+ suggestions: z.ZodArray<z.ZodString>;
360
+ body: z.ZodString;
361
+ reviewedAt: z.ZodISODateTime;
362
+ }, z.core.$strip>;
317
363
  };
318
364
  /**
319
365
  * Validate a payload against the schema for the given artifact kind.
@@ -39,6 +39,20 @@ export const ReviewFeedbackV1Schema = z.object({
39
39
  body: z.string(),
40
40
  reviewedAt: z.iso.datetime(),
41
41
  });
42
+ export const ImplementationReviewV1Schema = z.object({
43
+ schema_version: SchemaVersion,
44
+ specId: z.string(),
45
+ verdict: z.enum(['approved', 'changes-requested']),
46
+ reviewer: z.object({
47
+ kind: z.enum(['implementation-review-agent', 'human']),
48
+ agent: z.string().min(1),
49
+ verdict: z.enum(['approved', 'changes-requested']),
50
+ }),
51
+ blockers: z.array(z.string()),
52
+ suggestions: z.array(z.string()),
53
+ body: z.string(),
54
+ reviewedAt: z.iso.datetime(),
55
+ });
42
56
  export const ImplementationReportV1Schema = z.object({
43
57
  schema_version: SchemaVersion,
44
58
  specId: z.string(),
@@ -163,6 +177,7 @@ export const ARTIFACT_SCHEMAS = {
163
177
  review_feedback: ReviewFeedbackV1Schema,
164
178
  'implementation-report': ImplementationReportV1Schema,
165
179
  'validation-report': ValidationReportV1Schema,
180
+ implementation_review: ImplementationReviewV1Schema,
166
181
  };
167
182
  // ---------------------------------------------------------------------------
168
183
  // Validate function
@@ -4,6 +4,7 @@ import { join } from 'node:path';
4
4
  import { projectDataDir } from '../storage/base-store.js';
5
5
  import { specStore } from '../storage/index.js';
6
6
  import { ValidationReportV1Schema } from './handoff-artifacts/schemas.js';
7
+ import { AUTOMATED_VALIDATOR_AGENT } from './validator/validation-report-writer.js';
7
8
  import { rollbackTransitionSpec, transitionSpec, } from './spec-state-machine/transition-spec.js';
8
9
  import { acquireLock, releaseLock } from './safety/cross-process-lock.js';
9
10
  import { appendTransitionEvent, readTransitionLog } from '../storage/transition-log.js';
@@ -144,10 +145,10 @@ async function validateRequest(args) {
144
145
  const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
145
146
  if (!report.success ||
146
147
  report.data.passed ||
147
- report.data.reviewer.kind !== 'implementation-review-agent' ||
148
- report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
148
+ report.data.reviewer.kind !== 'automation' ||
149
+ report.data.reviewer.agent !== AUTOMATED_VALIDATOR_AGENT ||
149
150
  report.data.reviewer.verdict !== 'changes-requested') {
150
- return failure('validation report is not an independent changes-requested finding');
151
+ return failure('validation report is not an automated changes-requested finding');
151
152
  }
152
153
  const reviewDigest = digest(reportBytes);
153
154
  if (reviewDigest !== input.implementationReviewDigest) {
@@ -197,8 +198,8 @@ async function validateStoredBinding(args) {
197
198
  const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
198
199
  if (!report.success ||
199
200
  report.data.passed ||
200
- report.data.reviewer.kind !== 'implementation-review-agent' ||
201
- report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
201
+ report.data.reviewer.kind !== 'automation' ||
202
+ report.data.reviewer.agent !== AUTOMATED_VALIDATOR_AGENT ||
202
203
  report.data.reviewer.verdict !== 'changes-requested' ||
203
204
  digest(reportBytes) !== args.receipt.implementationReviewDigest) {
204
205
  return failure('implementation review no longer matches the durable receipt', 'RECONCILIATION_BINDING_CONFLICT');
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-aarch64-apple-darwin",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-aarch64-apple-darwin",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -17,15 +17,15 @@
17
17
  "platform": "darwin",
18
18
  "arch": "arm64",
19
19
  "libc": null,
20
- "artifactSha256": "b2d852dc52754af4bb30c62703566f1904ef5d36905a500ee93dd659d2a5e1e2",
20
+ "artifactSha256": "3944cdece78490684988774e3080412e52305165f34bec47d3b3793e4876f16d",
21
21
  "sbom": {
22
22
  "format": "CycloneDX-1.5",
23
23
  "path": "planu-core.darwin-arm64.node.sbom.json",
24
- "sha256": "f40d46dc1cafb9791a5abb6b22715025ed1a46ebbece82949b770e92009a33d0"
24
+ "sha256": "dfcb582019b086c81718b5099866e3d099fdfb26d1d768fdc4b6ccdee0e61e6d"
25
25
  },
26
26
  "provenance": {
27
27
  "builder": "scripts/build-rust-local.sh",
28
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
28
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
29
29
  "sourceTreeDirty": false,
30
30
  "sourceDateEpoch": 1,
31
31
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -34,6 +34,6 @@
34
34
  "signature": {
35
35
  "algorithm": "Ed25519",
36
36
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
37
- "value": "chGhyQXfG6qVz8U+TCAugdrFAW9pvbketCjVF0BESmKZlIMKyzq15V3r8QSuBOGqCs3OtuORM0R6VRJEreHlAA=="
37
+ "value": "YcxPQGO0lonpNPFA4QOaIliyLhn8uSXBgEYi8ebd6RokgmoKc+XSPdga7ZMkx5N7jiPJXAX8Hs6VtTs0SP3qAA=="
38
38
  }
39
39
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "b2d852dc52754af4bb30c62703566f1904ef5d36905a500ee93dd659d2a5e1e2"
14
+ "content": "3944cdece78490684988774e3080412e52305165f34bec47d3b3793e4876f16d"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-x86_64-apple-darwin",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-x86_64-apple-darwin",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -17,15 +17,15 @@
17
17
  "platform": "darwin",
18
18
  "arch": "x64",
19
19
  "libc": null,
20
- "artifactSha256": "6a08bbe0d147a81c8f9094651912b89f2a7bbd9d4ab09e8f713db3493469ba68",
20
+ "artifactSha256": "bad5403aa1169630e7f34c73a202ee9419e845e7aedc1833f3c10ce9e316d019",
21
21
  "sbom": {
22
22
  "format": "CycloneDX-1.5",
23
23
  "path": "planu-core.darwin-x64.node.sbom.json",
24
- "sha256": "2aa4b1f6dfe3be29bfafa818692dd08f629763eb02b7f0119a0ba828c5de11d8"
24
+ "sha256": "9bd682d7d30035b52bd89a7ca52f6a2e72997bac78162a42a548db36d95f0402"
25
25
  },
26
26
  "provenance": {
27
27
  "builder": "scripts/build-rust-local.sh",
28
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
28
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
29
29
  "sourceTreeDirty": false,
30
30
  "sourceDateEpoch": 1,
31
31
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -34,6 +34,6 @@
34
34
  "signature": {
35
35
  "algorithm": "Ed25519",
36
36
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
37
- "value": "gVWSBJ6t5K9Jl6iC3VDEqApdsZXpGKK/+Qv75Ijp6JinDPZvqO8ESVeO4RonNELQljv5r0DFgvR+5Z+IfsNxAg=="
37
+ "value": "Ief0G5GcpRr5dx8XXAvuBSoSypSj/OaTvm5LKMjXePMorS3UbOxNUNAVTwmK50xU3wNAXAEjQBItrH3takLLBg=="
38
38
  }
39
39
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "6a08bbe0d147a81c8f9094651912b89f2a7bbd9d4ab09e8f713db3493469ba68"
14
+ "content": "bad5403aa1169630e7f34c73a202ee9419e845e7aedc1833f3c10ce9e316d019"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-aarch64-unknown-linux-gnu",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-aarch64-unknown-linux-gnu",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "linux",
17
17
  "arch": "arm64",
18
18
  "libc": "glibc",
19
- "artifactSha256": "1d9dfeccdff69d18bbddbd0824dca6742c4a3ecd9e66b52cace270eddb900444",
19
+ "artifactSha256": "5c95d0f8cc4c1206d0540582bba0c6d8dddb46dd7777f197e55bfa86a605b18f",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.linux-arm64-gnu.node.sbom.json",
23
- "sha256": "9b9271f9439cc9be0737167da1453b37dd2607891da718aa4dd355edf57286fe"
23
+ "sha256": "35767b4792c75c124fba6f443ab2829a592b783fc7e9768ae7bd7acd22d338c1"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "bo+EsDcaAkbFJiPyt3Fdenl+cKnPfTysWT7PfJKVAxYpdyWFv9prn7G7Hb8LwVjgsUulNv/v7+Uwx+R0O9ywBw=="
36
+ "value": "aR4GV3GZ5PuubrvY3jLlMuTbSNarWsqsl3Z0uLyMfMBedf4RkHwgAc5H/qSRJl1qc/IR2ewNo//1VVwaW821DQ=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "1d9dfeccdff69d18bbddbd0824dca6742c4a3ecd9e66b52cace270eddb900444"
14
+ "content": "5c95d0f8cc4c1206d0540582bba0c6d8dddb46dd7777f197e55bfa86a605b18f"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-aarch64-unknown-linux-musl",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-aarch64-unknown-linux-musl",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "linux",
17
17
  "arch": "arm64",
18
18
  "libc": "musl",
19
- "artifactSha256": "998d8d6076161d998a11a57c2c06f291e4f73caa638bd530c6391926456c9e88",
19
+ "artifactSha256": "403cda11d886b0e61105413cc6b52b5c75132a36c7e3bd9696ffac7ecfaa8c38",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.linux-arm64-musl.node.sbom.json",
23
- "sha256": "1d0bc13dc8e4c207ae2a6eb857b1be214cce702f2be06b1538078e54a2285f8d"
23
+ "sha256": "fdd8d79891f8f1d52941dc4695e3357eaf14c2839fe9029ea0eaac8bed70dea3"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "E3FUnw4F0Mo18fXF12PdntrhwZ5PpG3jrUnwRIo+xL62Ij5XDe0CiF8n9cSBALGLSUCeytaeYPWYsPjq5DO3CQ=="
36
+ "value": "+JcZN2BHlWWb2q5cjOnbxJR/TlGmiDrDQJOWy+lpi2cugScY7RvNqUlaYcDKR/yyuA+NbUqRaSzC3aZT4IsKDQ=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "998d8d6076161d998a11a57c2c06f291e4f73caa638bd530c6391926456c9e88"
14
+ "content": "403cda11d886b0e61105413cc6b52b5c75132a36c7e3bd9696ffac7ecfaa8c38"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-x86_64-unknown-linux-gnu",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-x86_64-unknown-linux-gnu",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "linux",
17
17
  "arch": "x64",
18
18
  "libc": "glibc",
19
- "artifactSha256": "82aad07391992062c26f860f5ef9beab126048cae4c56c1c3eb097c7edad674d",
19
+ "artifactSha256": "5589735f4b9fd914dd2caeb85451cb43e044aabd21fbf576b7848ca5307dca97",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.linux-x64-gnu.node.sbom.json",
23
- "sha256": "2e5cd34fb3b8e6bf81f6da6c09a7a777f274ee7c6a55696d7541622383e250e1"
23
+ "sha256": "5ff8867f8dc53d4ba510e8a1ddd93737dd8ad8aa5d0bcb14a99e881637c375b7"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "di+vGzqgsPMmHH/6UFcIElY003sMfb/Q/MduHuxY06aY6JT82QNuLNx1mkV3+MYhJhynyMsaBxt7Vin/FTPUBA=="
36
+ "value": "344JYOjoTT7KOMOf+hHJibJndPiOyB3kVhEVf/OgRvyS0itLL3j4R/EFytlfqJsKfGU18TBs0TBgGhE02MedCg=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "82aad07391992062c26f860f5ef9beab126048cae4c56c1c3eb097c7edad674d"
14
+ "content": "5589735f4b9fd914dd2caeb85451cb43e044aabd21fbf576b7848ca5307dca97"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-x86_64-unknown-linux-musl",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-x86_64-unknown-linux-musl",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "linux",
17
17
  "arch": "x64",
18
18
  "libc": "musl",
19
- "artifactSha256": "91d71782f92e585b90444463370ec9d600b25a52a1d892b903676d4c8fddd2ad",
19
+ "artifactSha256": "714caf18004f9bacf3a9c343a13799b1d348a74b8d37e03fa6cf9a7589fc9cd7",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.linux-x64-musl.node.sbom.json",
23
- "sha256": "5fa1e3aed4b54c4fef050d7a541c2bbbed4ffec3fc21667818df15572087e7a4"
23
+ "sha256": "bbb4d91ec1bb637a3e29e2b8b0d16248a33bdfb65050b932f7b3aacee5ecfbe2"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "N+t5MBnx7wr/JyNeJ+8887ZHguXLM9IgP8UT+p5CqZsooNBCHKXErn9vUyRWNHdR8bJ0zqVIOnWMq44Lmh/PDw=="
36
+ "value": "nKxw4OqHVfIUJsWGwFZo7yi5AVBpl5Fxgn1isO5nXRTFsvrQIGZ349nnmv7GBRqlOatcBhlicO3ApL629AKbBA=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "91d71782f92e585b90444463370ec9d600b25a52a1d892b903676d4c8fddd2ad"
14
+ "content": "714caf18004f9bacf3a9c343a13799b1d348a74b8d37e03fa6cf9a7589fc9cd7"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-aarch64-pc-windows-msvc",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-aarch64-pc-windows-msvc",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "win32",
17
17
  "arch": "arm64",
18
18
  "libc": null,
19
- "artifactSha256": "42242fb16dcedcff4ccdc5a8d359279ba985a916eab84e803b98d1840cc2a59e",
19
+ "artifactSha256": "b847ff92831078fb9d108cbf191eb39307c49a25d404da070db732dded3fc894",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.win32-arm64-msvc.node.sbom.json",
23
- "sha256": "cbdd51f8b9ff70711b39912839dc7d03b1670056ab8909aa46d2b90ad5e25cbb"
23
+ "sha256": "eea89ce4eb73df92230c4507c0a182332df56d16e8a32e5409f34a95a7a7f05f"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "lYI+Oujs1RPS2A9qzkJC5Jf2ISaSxjMJkXH44GgpLJfNXsvSYTBFx6lq+gpzo2R0tiUvF7JSWG4+WfI2I5KYCg=="
36
+ "value": "7TTGhX99A+J+lpXk1+fUcDLgxki0Vmnp9nVkfJkEKS0cgGGLAVAnHAEF0uawZjywCJF1MaYSMn6jEmUUqVTJCg=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "42242fb16dcedcff4ccdc5a8d359279ba985a916eab84e803b98d1840cc2a59e"
14
+ "content": "b847ff92831078fb9d108cbf191eb39307c49a25d404da070db732dded3fc894"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "cliVersion": "5.3.26",
4
- "engineVersion": "5.3.26",
5
- "buildId": "1-5.3.26-x86_64-pc-windows-msvc",
3
+ "cliVersion": "5.3.27",
4
+ "engineVersion": "5.3.27",
5
+ "buildId": "1-5.3.27-x86_64-pc-windows-msvc",
6
6
  "protocolAbi": 1,
7
7
  "capabilityAbi": 2,
8
8
  "nodeApiAbi": 4,
@@ -16,15 +16,15 @@
16
16
  "platform": "win32",
17
17
  "arch": "x64",
18
18
  "libc": null,
19
- "artifactSha256": "a83417f73e4210940eef71c672d093af314be694ac60b961be41cf126f16ab8b",
19
+ "artifactSha256": "557925ec6e5f7f32feb1bb42683e058e974b20032ff19a0807a3c57059af748d",
20
20
  "sbom": {
21
21
  "format": "CycloneDX-1.5",
22
22
  "path": "planu-core.win32-x64-msvc.node.sbom.json",
23
- "sha256": "a7eb5568757d44ba58a01b986573da423b7caa512d122e0de42fbe4f8a6d23c6"
23
+ "sha256": "e61c9fd311db17fcac42bec3d7fa6ab61bf2c7082ac80196bfb6b9ba54977fda"
24
24
  },
25
25
  "provenance": {
26
26
  "builder": "scripts/build-rust-local.sh",
27
- "sourceCommit": "2419aedc35345da335c3469eaf80b19f428fd53b",
27
+ "sourceCommit": "79788c0b8d9bfbc05294316f3546693af74482ab",
28
28
  "sourceTreeDirty": false,
29
29
  "sourceDateEpoch": 1,
30
30
  "rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
@@ -33,6 +33,6 @@
33
33
  "signature": {
34
34
  "algorithm": "Ed25519",
35
35
  "keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
36
- "value": "cOjoj33opvadNE+ow9eezowf1I8vVGssK+KioLL5P2HfMP6UmwG0MVJNCpGj4h6JMF+qnu4/k7WMEVHmEcSgAA=="
36
+ "value": "O3ZXl5L6qzts4ZRg9WDNIHioY1qH1I/yx9V/aMi0Bb5/Ct3hq+eMS+8njmwY5p9qbhGzQq2yYf4DwJ3vbWH8DQ=="
37
37
  }
38
38
  }
@@ -4,14 +4,14 @@
4
4
  "version": 1,
5
5
  "metadata": {
6
6
  "component": {
7
- "bom-ref": "pkg:cargo/planu-core@5.3.26",
7
+ "bom-ref": "pkg:cargo/planu-core@5.3.27",
8
8
  "type": "library",
9
9
  "name": "planu-core",
10
- "version": "5.3.26",
10
+ "version": "5.3.27",
11
11
  "hashes": [
12
12
  {
13
13
  "alg": "SHA-256",
14
- "content": "a83417f73e4210940eef71c672d093af314be694ac60b961be41cf126f16ab8b"
14
+ "content": "557925ec6e5f7f32feb1bb42683e058e974b20032ff19a0807a3c57059af748d"
15
15
  }
16
16
  ]
17
17
  }
@@ -1106,7 +1106,7 @@
1106
1106
  "dependsOn": []
1107
1107
  },
1108
1108
  {
1109
- "ref": "pkg:cargo/planu-core@5.3.26",
1109
+ "ref": "pkg:cargo/planu-core@5.3.27",
1110
1110
  "dependsOn": [
1111
1111
  "pkg:cargo/core-foundation@0.10.1",
1112
1112
  "pkg:cargo/hmac@0.12.1",
@@ -66,7 +66,7 @@ function splitClauses(text) {
66
66
  .map((clause) => clause.trim())
67
67
  .filter(Boolean);
68
68
  }
69
- const AFFIRMATIVE_ACTION_VERBS = /\b(?:changes?|modif(?:y|ies)|alters?|adds?|introduces?|implements?|enables?|exposes?|exposed|mutat(?:e|es|ing))\b/;
69
+ const AFFIRMATIVE_ACTION_VERBS = /\b(?:build(?:s|ing)?|chang(?:e|es|ing)|modif(?:y|ies|ying)|alter(?:s|ing)?|add(?:s|ing)?|introduc(?:e|es|ing)|implement(?:s|ing)?|enabl(?:e|es|ing)|expos(?:e|es|ing|ed)|mutat(?:e|es|ing)|rewrit(?:e|es|ing|ten))\b/;
70
70
  function assertsForbiddenAction(text) {
71
71
  return AFFIRMATIVE_ACTION_VERBS.test(normalize(text));
72
72
  }
@@ -1,6 +1,7 @@
1
1
  import type { Spec } from '../../types/index.js';
2
2
  import type { ValidationReportV1 } from '../../types/handoff-artifacts.js';
3
3
  import type { MinimalImplementationReport } from '../../types/minimal-implementation-gate.js';
4
+ export declare const AUTOMATED_VALIDATOR_AGENT = "planu-automated-validator";
4
5
  export declare function writeImplementationReviewReport(input: {
5
6
  projectId: string;
6
7
  specId: string;
@@ -1,5 +1,6 @@
1
1
  import { appendArtifact, readArtifact } from '../handoff-artifacts/io.js';
2
2
  import { runSpecCompliance, } from './spec-compliance-runner.js';
3
+ export const AUTOMATED_VALIDATOR_AGENT = 'planu-automated-validator';
3
4
  export async function writeImplementationReviewReport(input) {
4
5
  const specCompliance = await runSpecCompliance(input.spec, input.projectPath, input.signal, input.projectId);
5
6
  const reportScore = resolveExecutableValidationScore(input.score, specCompliance);
@@ -13,8 +14,8 @@ export async function writeImplementationReviewReport(input) {
13
14
  const mergedGates = await preserveUnobservedGates(input, gates);
14
15
  const passed = mergedGates.every((gate) => gate.passed);
15
16
  const reviewer = {
16
- kind: 'implementation-review-agent',
17
- agent: 'planu-implementation-reviewer',
17
+ kind: 'automation',
18
+ agent: AUTOMATED_VALIDATOR_AGENT,
18
19
  verdict: passed ? 'approved' : 'changes-requested',
19
20
  };
20
21
  const reportCompliance = toValidationReportCompliance(specCompliance);
@@ -107,6 +107,7 @@ export declare function checkValidationReportGate(specId: string, projectId: str
107
107
  export declare function readApprovedValidationReportGate(specId: string, projectId: string, force: boolean | undefined): Promise<ValidationReportGateResult>;
108
108
  /** SPEC-1051: Approval requires a dedicated spec reviewer artifact. */
109
109
  export declare function checkSpecReviewGate(specId: string, projectId: string, _forceApprove: boolean | undefined): Promise<ToolResult | null>;
110
+ export declare function checkImplementationReviewGate(specId: string, projectId: string, _force: boolean | undefined): Promise<ToolResult | null>;
110
111
  /**
111
112
  * SPEC-335: Combined done-gates runner — DoD + security.
112
113
  * When force=true, gates are not blocking but failing items are recorded in
@@ -12,11 +12,12 @@ import { addLesson, getLessons } from '../../storage/lessons-store.js';
12
12
  import { dispatchFeedbackEvent } from '../learn.js';
13
13
  import { auditDeps } from '../../engine/dep-auditor/index.js';
14
14
  import { withEscalation } from '../../engine/escalator/with-escalation.js';
15
- import { writeImplementationReviewReport } from '../../engine/validator/validation-report-writer.js';
15
+ import { AUTOMATED_VALIDATOR_AGENT, writeImplementationReviewReport, } from '../../engine/validator/validation-report-writer.js';
16
16
  import { formatKeyValue } from '../output-formatter.js';
17
17
  import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
18
18
  import { verifyCurrentDoneReceipt } from './done-receipt-verifier.js';
19
19
  import { projectDataDir } from '../../storage/base-store.js';
20
+ import { readTransitionLog } from '../../storage/transition-log.js';
20
21
  import { ValidationReportV1Schema } from '../../engine/handoff-artifacts/schemas.js';
21
22
  export { completedJobPublishesReceipt } from './done-receipt-verifier.js';
22
23
  /**
@@ -447,10 +448,10 @@ async function checkDoneReviewDigest(specId, projectId, implementationReviewDige
447
448
  !parsed.data.passed ||
448
449
  parsed.data.gates.some((gate) => !gate.passed) ||
449
450
  parsed.data.minimalityReport?.blocked === true ||
450
- parsed.data.reviewer.kind !== 'implementation-review-agent' ||
451
- parsed.data.reviewer.agent !== 'planu-implementation-reviewer' ||
451
+ parsed.data.reviewer.kind !== 'automation' ||
452
+ parsed.data.reviewer.agent !== AUTOMATED_VALIDATOR_AGENT ||
452
453
  parsed.data.reviewer.verdict !== 'approved') {
453
- return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report schema or independent reviewer evidence is invalid.');
454
+ return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report schema or automated validator evidence is invalid.');
454
455
  }
455
456
  return null;
456
457
  }
@@ -616,6 +617,116 @@ export async function checkSpecReviewGate(specId, projectId, _forceApprove) {
616
617
  });
617
618
  }
618
619
  }
620
+ export async function checkImplementationReviewGate(specId, projectId, _force) {
621
+ try {
622
+ const { readArtifact } = await import('../../engine/handoff-artifacts/io.js');
623
+ const result = await readArtifact({ projectId, specId, kind: 'implementation_review' });
624
+ if (!result.ok) {
625
+ const firstErr = result.errors[0];
626
+ return implementationReviewGateError({
627
+ specId,
628
+ error: firstErr?.code === 'ARTIFACT_NOT_FOUND'
629
+ ? 'implementation_review_missing'
630
+ : 'implementation_review_invalid',
631
+ message: firstErr?.code === 'ARTIFACT_NOT_FOUND'
632
+ ? 'No implementation review exists for this spec. An independent planu-implementation-reviewer must review the implementation and write its own evidence before done.'
633
+ : 'Implementation review evidence is malformed or uses an obsolete schema.',
634
+ blockers: [],
635
+ fixHint: firstErr?.code === 'ARTIFACT_NOT_FOUND'
636
+ ? 'Have planu-implementation-reviewer write implementation_review.json (ImplementationReviewV1) into the handoff store, then retry update_status(done).'
637
+ : 'Have planu-implementation-reviewer rewrite implementation_review.json as valid ImplementationReviewV1 evidence, then retry done.',
638
+ });
639
+ }
640
+ const review = result.payload;
641
+ if (isLifecycleStubBody(specId, review.body)) {
642
+ return implementationReviewGateError({
643
+ specId,
644
+ error: 'implementation_review_stub',
645
+ message: 'Implementation review evidence is a lifecycle-generated stub, not evidence from a real reviewer.',
646
+ blockers: [],
647
+ fixHint: 'Replace the stub with genuine planu-implementation-reviewer evidence in the handoff store, then retry done.',
648
+ });
649
+ }
650
+ if (review.verdict !== 'approved' || review.reviewer.verdict !== 'approved') {
651
+ return implementationReviewGateError({
652
+ specId,
653
+ error: 'implementation_review_changes_requested',
654
+ message: 'Implementation reviewer requested changes. Done is blocked until the review passes.',
655
+ blockers: review.blockers,
656
+ fixHint: 'Resolve the implementation review blockers and have planu-implementation-reviewer write updated evidence, then retry done.',
657
+ });
658
+ }
659
+ if (review.reviewer.kind !== 'implementation-review-agent' ||
660
+ review.reviewer.agent !== 'planu-implementation-reviewer') {
661
+ return implementationReviewGateError({
662
+ specId,
663
+ error: 'implementation_review_wrong_reviewer',
664
+ message: 'Done requires the recognized independent implementation reviewer identity.',
665
+ blockers: [
666
+ `Reviewer was ${review.reviewer.agent}. Expected planu-implementation-reviewer.`,
667
+ ],
668
+ fixHint: 'Have planu-implementation-reviewer (with the recognized identity) write the review artifact, then retry done.',
669
+ });
670
+ }
671
+ const implementerActor = await findImplementingActor(specId, projectId);
672
+ if (implementerActor !== undefined && implementerActor === review.reviewer.agent) {
673
+ return implementationReviewGateError({
674
+ specId,
675
+ error: 'implementation_review_self_review',
676
+ message: 'The implementation reviewer must be a different identity from the implementer. Self-review is blocked.',
677
+ blockers: [`Implementer and reviewer share the identity "${implementerActor}".`],
678
+ fixHint: 'Have a different, independent agent write the implementation review evidence, then retry done.',
679
+ });
680
+ }
681
+ return null;
682
+ }
683
+ catch (err) {
684
+ /* reliability-optional: IMPLEMENTATION_REVIEW_GATE_READ — typed gate error blocks done */
685
+ reportClassifiedDegradation('IMPLEMENTATION_REVIEW_GATE_READ', err);
686
+ return implementationReviewGateError({
687
+ specId,
688
+ error: 'implementation_review_unreadable',
689
+ message: `Could not read implementation review evidence: ${err instanceof Error ? err.message : String(err)}`,
690
+ blockers: [],
691
+ fixHint: 'Fix the artifact store issue so implementation_review.json is readable, then retry update_status(done).',
692
+ });
693
+ }
694
+ }
695
+ async function findImplementingActor(specId, projectId) {
696
+ const entries = await readTransitionLog(projectId, specId);
697
+ for (let i = entries.length - 1; i >= 0; i--) {
698
+ const entry = entries[i];
699
+ if (entry?.to === 'implementing' && entry.actor) {
700
+ return entry.actor;
701
+ }
702
+ }
703
+ return undefined;
704
+ }
705
+ function implementationReviewGateError(args) {
706
+ const artifactPath = `external Planu project data: handoffs/${args.specId}/implementation_review.json`;
707
+ return {
708
+ content: [
709
+ {
710
+ type: 'text',
711
+ text: formatKeyValue({
712
+ error: args.error,
713
+ message: args.message,
714
+ artifactPath,
715
+ blockers: args.blockers.length,
716
+ firstBlocker: args.blockers[0],
717
+ fixHint: args.fixHint,
718
+ }, 'Implementation review gate failed'),
719
+ },
720
+ ],
721
+ isError: true,
722
+ structuredContent: {
723
+ error: args.error,
724
+ code: 422,
725
+ context: { specId: args.specId, artifactPath, blockers: args.blockers },
726
+ fixHint: args.fixHint,
727
+ },
728
+ };
729
+ }
619
730
  function validationReportGateError(args) {
620
731
  const artifactPath = `external Planu project data: handoffs/${args.specId}/validation-report.json`;
621
732
  return {
@@ -673,6 +784,14 @@ function specReviewGateError(args) {
673
784
  */
674
785
  export async function checkDoneGates(spec, specId, projectId, projectPath, _force, _forceReason = 'No force reason provided', verifyReceipt = verifyCurrentDoneReceipt, implementationReviewDigest) {
675
786
  try {
787
+ const reviewGateError = await checkImplementationReviewGate(specId, projectId, _force);
788
+ if (reviewGateError) {
789
+ return {
790
+ blocked: reviewGateError,
791
+ forcedBypassWarning: null,
792
+ freshnessLease: null,
793
+ };
794
+ }
676
795
  if (implementationReviewDigest !== undefined) {
677
796
  const digestError = await checkDoneReviewDigest(specId, projectId, implementationReviewDigest);
678
797
  if (digestError) {
@@ -1,6 +1,6 @@
1
1
  import type { Spec } from './spec/core.js';
2
2
  /** Artifact kinds produced/consumed by each role transition */
3
- export type ArtifactKind = 'intake' | 'spec.lock' | 'review_feedback' | 'implementation-report' | 'validation-report';
3
+ export type ArtifactKind = 'intake' | 'spec.lock' | 'review_feedback' | 'implementation-report' | 'validation-report' | 'implementation_review';
4
4
  /** Triagier → Elicitor: describes the intent to be elaborated */
5
5
  export interface IntakeV1 {
6
6
  schema_version: string;
@@ -39,6 +39,21 @@ export interface ReviewFeedbackV1 {
39
39
  body: string;
40
40
  reviewedAt: string;
41
41
  }
42
+ /** Independent reviewer → Planner: evidence that an implementation review actually happened */
43
+ export interface ImplementationReviewV1 {
44
+ schema_version: string;
45
+ specId: string;
46
+ verdict: 'approved' | 'changes-requested';
47
+ reviewer: {
48
+ kind: 'implementation-review-agent' | 'human';
49
+ agent: string;
50
+ verdict: 'approved' | 'changes-requested';
51
+ };
52
+ blockers: string[];
53
+ suggestions: string[];
54
+ body: string;
55
+ reviewedAt: string;
56
+ }
42
57
  /** Implementer → Validator: result of implementation run */
43
58
  export interface ImplementationReportV1 {
44
59
  schema_version: string;
@@ -125,6 +140,7 @@ export interface ArtifactPayloadMap {
125
140
  review_feedback: ReviewFeedbackV1;
126
141
  'implementation-report': ImplementationReportV1;
127
142
  'validation-report': ValidationReportV1;
143
+ implementation_review: ImplementationReviewV1;
128
144
  }
129
145
  export type ArtifactPayload<K extends ArtifactKind> = ArtifactPayloadMap[K];
130
146
  import type { MinimalImplementationReport } from './minimal-implementation-gate.js';
@@ -1,5 +1,5 @@
1
1
  /** Event types recorded in the transition log. */
2
- export type TransitionEventType = 'transitioned_to_terminal' | 'frontmatter_resealed' | 'reopen' | 'terminal_drift_detected' | 'transition' | 'handoff.intake' | 'handoff.spec.lock' | 'handoff.review_feedback' | 'handoff.implementation-report' | 'handoff.validation-report' | 'retro_audit' | 'tdd_red_locked' | 'tdd_green_achieved' | 'status_reconciled' | 'housekeeping' | 'ghost_spec_quarantined';
2
+ export type TransitionEventType = 'transitioned_to_terminal' | 'frontmatter_resealed' | 'reopen' | 'terminal_drift_detected' | 'transition' | 'handoff.intake' | 'handoff.spec.lock' | 'handoff.review_feedback' | 'handoff.implementation-report' | 'handoff.validation-report' | 'handoff.implementation_review' | 'retro_audit' | 'tdd_red_locked' | 'tdd_green_achieved' | 'status_reconciled' | 'housekeeping' | 'ghost_spec_quarantined';
3
3
  /** SPEC-734: Outcome of a gate execution in a transition. */
4
4
  export type GateOutcome = 'pass' | 'fail' | 'skip' | 'forced';
5
5
  /** SPEC-734: Result of verifying the hash chain integrity of a project's transition log. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.3.26",
3
+ "version": "5.3.27",
4
4
  "description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,14 +36,14 @@
36
36
  "packageName": "@planu/core"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@planu/core-darwin-arm64": "5.3.26",
40
- "@planu/core-darwin-x64": "5.3.26",
41
- "@planu/core-linux-arm64-gnu": "5.3.26",
42
- "@planu/core-linux-arm64-musl": "5.3.26",
43
- "@planu/core-linux-x64-gnu": "5.3.26",
44
- "@planu/core-linux-x64-musl": "5.3.26",
45
- "@planu/core-win32-arm64-msvc": "5.3.26",
46
- "@planu/core-win32-x64-msvc": "5.3.26"
39
+ "@planu/core-darwin-arm64": "5.3.27",
40
+ "@planu/core-darwin-x64": "5.3.27",
41
+ "@planu/core-linux-arm64-gnu": "5.3.27",
42
+ "@planu/core-linux-arm64-musl": "5.3.27",
43
+ "@planu/core-linux-x64-gnu": "5.3.27",
44
+ "@planu/core-linux-x64-musl": "5.3.27",
45
+ "@planu/core-win32-arm64-msvc": "5.3.27",
46
+ "@planu/core-win32-x64-msvc": "5.3.27"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=24.0.0"
package/planu-native.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "5.3.26",
4
+ "version": "5.3.27",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
package/planu-plugin.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dev.planu.cli",
3
3
  "displayName": "Planu — Spec Driven Development",
4
4
  "description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
5
- "version": "5.3.26",
5
+ "version": "5.3.27",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",