@planu/cli 5.3.29 → 5.3.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/engine/evidence-gates/lifecycle-gate.d.ts +1 -1
- package/dist/engine/evidence-gates/lifecycle-gate.js +87 -33
- package/dist/engine/validation/durable-validation.d.ts +7 -0
- package/dist/engine/validation/durable-validation.js +25 -0
- package/dist/tools/update-status/done-receipt-verifier.d.ts +1 -8
- package/dist/tools/update-status/done-receipt-verifier.js +12 -27
- package/dist/tools/update-status/evidence-gate.js +10 -2
- package/dist/types/evidence-gates.d.ts +11 -1
- package/package.json +1 -1
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [5.3.31] - 2026-08-21
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(spec-1584): exclude .claude/worktrees from Stryker sandbox to survive residual worktrees
|
|
5
|
+
|
|
6
|
+
### Chores
|
|
7
|
+
- chore(planu): file SPEC-1584 (Stryker worktree dangling-symlink) draft
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [5.3.30] - 2026-08-20
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
- fix(spec-1583): drop redundant access() precheck that TCC-fails website-proof
|
|
14
|
+
- fix(spec-1581): re-verify receipt+log integrity on evidence-only drift
|
|
15
|
+
- fix(spec-1581): done gate rebinds evidence-only traceability drift
|
|
16
|
+
|
|
17
|
+
### Chores
|
|
18
|
+
- chore(planu): SPEC-1581 done + SPEC-1582 draft filed
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
## [5.3.29] - 2026-08-20
|
|
2
22
|
|
|
3
23
|
### Bug Fixes
|
|
@@ -10,5 +10,5 @@ export declare function checkLifecycleEvidenceGate(args: {
|
|
|
10
10
|
criteria: string[];
|
|
11
11
|
artifacts: EvidenceArtifacts;
|
|
12
12
|
receiptContext?: EvidenceReceiptContext;
|
|
13
|
-
}): EvidenceGateResult
|
|
13
|
+
}): Promise<EvidenceGateResult>;
|
|
14
14
|
//# sourceMappingURL=lifecycle-gate.d.ts.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { isAbsolute, join } from 'node:path';
|
|
1
2
|
import { evidenceArtifactCollectionDigest, evidenceArtifactDigest } from './artifact-reader.js';
|
|
2
3
|
import { hasAnyAffirmedMatch, stripMetaAnalysisText, stripNonContractText, } from '../text-signal-boundaries.js';
|
|
3
4
|
import { createCriterionIdentity } from '../criterion-identity.js';
|
|
5
|
+
import { computeDurableValidationBindings, doneReceiptBindingsDifferOnlyInEvidence, toValidationReceiptBindings, writeValidationArtifactProvenance, } from '../validation/durable-validation.js';
|
|
4
6
|
const CONTRACT_HINTS = {
|
|
5
7
|
api: [
|
|
6
8
|
/\b(?:public|external|rest|http)[ -]?api\b/i,
|
|
@@ -26,55 +28,107 @@ function isNonTrivial(spec) {
|
|
|
26
28
|
function hasText(value) {
|
|
27
29
|
return value !== undefined && value.trim().length > 0;
|
|
28
30
|
}
|
|
29
|
-
function
|
|
30
|
-
const receipt = context.receipt;
|
|
31
|
+
function collectProvenanceCandidates(artifacts) {
|
|
31
32
|
const candidates = [];
|
|
32
33
|
if (artifacts.discovery) {
|
|
33
|
-
candidates.push({
|
|
34
|
-
label: 'discovery',
|
|
35
|
-
artifact: artifacts.discovery,
|
|
36
|
-
expectedDigest: receipt.bindings.artifactDigests.discovery,
|
|
37
|
-
});
|
|
34
|
+
candidates.push({ label: 'discovery', artifact: artifacts.discovery, digestKey: 'discovery' });
|
|
38
35
|
}
|
|
39
36
|
if (artifacts.taskPlan) {
|
|
40
|
-
candidates.push({
|
|
41
|
-
label: 'task plan',
|
|
42
|
-
artifact: artifacts.taskPlan,
|
|
43
|
-
expectedDigest: receipt.bindings.artifactDigests.taskPlan,
|
|
44
|
-
});
|
|
37
|
+
candidates.push({ label: 'task plan', artifact: artifacts.taskPlan, digestKey: 'taskPlan' });
|
|
45
38
|
}
|
|
46
39
|
if (artifacts.traceabilityMatrix) {
|
|
47
40
|
candidates.push({
|
|
48
41
|
label: 'traceability matrix',
|
|
49
42
|
artifact: artifacts.traceabilityMatrix,
|
|
50
|
-
|
|
43
|
+
digestKey: 'traceability',
|
|
51
44
|
});
|
|
52
45
|
}
|
|
53
46
|
for (const artifact of artifacts.contractValidations.slice(0, 1)) {
|
|
54
47
|
candidates.push({
|
|
55
48
|
label: 'contract validation collection',
|
|
56
49
|
artifact,
|
|
57
|
-
|
|
50
|
+
digestKey: 'contractValidation',
|
|
58
51
|
actualDigest: evidenceArtifactCollectionDigest(artifacts.contractValidations),
|
|
59
52
|
});
|
|
60
53
|
}
|
|
61
|
-
return candidates
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
return candidates;
|
|
55
|
+
}
|
|
56
|
+
function observedDigestOf(candidate) {
|
|
57
|
+
return candidate.actualDigest ?? evidenceArtifactDigest(candidate.artifact);
|
|
58
|
+
}
|
|
59
|
+
function candidateMatchesReceipt(candidate, receipt) {
|
|
60
|
+
const provenance = candidate.artifact.provenance;
|
|
61
|
+
const observedDigest = observedDigestOf(candidate);
|
|
62
|
+
const expectedDigest = receipt.bindings.artifactDigests[candidate.digestKey];
|
|
63
|
+
return (provenance?.receiptId === receipt.receiptId &&
|
|
64
|
+
provenance.specId === receipt.bindings.specId &&
|
|
65
|
+
provenance.issuerId === receipt.issuer &&
|
|
66
|
+
provenance.artifactDigest === observedDigest &&
|
|
67
|
+
provenance.artifactDigest === expectedDigest);
|
|
68
|
+
}
|
|
69
|
+
function candidateIdentityMatchesReceipt(candidate, receipt) {
|
|
70
|
+
const provenance = candidate.artifact.provenance;
|
|
71
|
+
return (provenance?.receiptId === receipt.receiptId &&
|
|
72
|
+
provenance.specId === receipt.bindings.specId &&
|
|
73
|
+
provenance.issuerId === receipt.issuer);
|
|
74
|
+
}
|
|
75
|
+
async function resolveObservedBindings(receipt, context) {
|
|
76
|
+
if (!context.projectPath || !context.specPath) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const projectPath = context.projectPath;
|
|
81
|
+
const specPath = isAbsolute(context.specPath)
|
|
82
|
+
? context.specPath
|
|
83
|
+
: join(projectPath, context.specPath);
|
|
84
|
+
const observed = await computeDurableValidationBindings({
|
|
85
|
+
canonicalProjectId: receipt.bindings.projectId,
|
|
86
|
+
specId: receipt.bindings.specId,
|
|
87
|
+
projectPath,
|
|
88
|
+
specPath,
|
|
89
|
+
});
|
|
90
|
+
return toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function provenanceIssues(artifacts, context) {
|
|
97
|
+
const receipt = context.receipt;
|
|
98
|
+
const candidates = collectProvenanceCandidates(artifacts);
|
|
99
|
+
const failing = candidates.filter((candidate) => !candidateMatchesReceipt(candidate, receipt));
|
|
100
|
+
if (failing.length === 0) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const observedBindings = await resolveObservedBindings(receipt, context);
|
|
104
|
+
const reconcilable = observedBindings && doneReceiptBindingsDifferOnlyInEvidence(observedBindings, receipt.bindings)
|
|
105
|
+
? observedBindings
|
|
106
|
+
: undefined;
|
|
107
|
+
const issues = [];
|
|
108
|
+
let reconciled = false;
|
|
109
|
+
for (const candidate of failing) {
|
|
110
|
+
const canReconcile = reconcilable !== undefined &&
|
|
111
|
+
candidateIdentityMatchesReceipt(candidate, receipt) &&
|
|
112
|
+
observedDigestOf(candidate) === reconcilable.artifactDigests[candidate.digestKey];
|
|
113
|
+
if (!canReconcile) {
|
|
114
|
+
issues.push({
|
|
115
|
+
code: 'evidence_provenance_invalid',
|
|
116
|
+
message: `${candidate.label} is not bound to the current Planu validation receipt.`,
|
|
117
|
+
});
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!reconciled) {
|
|
121
|
+
await writeValidationArtifactProvenance({
|
|
122
|
+
canonicalProjectId: receipt.bindings.projectId,
|
|
123
|
+
specId: receipt.bindings.specId,
|
|
124
|
+
receiptId: receipt.receiptId,
|
|
125
|
+
issuerId: receipt.issuer,
|
|
126
|
+
artifactDigests: reconcilable.artifactDigests,
|
|
127
|
+
});
|
|
128
|
+
reconciled = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return issues;
|
|
78
132
|
}
|
|
79
133
|
export function inferRequiredContractEvidence(spec, criteria) {
|
|
80
134
|
const text = stripMetaAnalysisText(stripNonContractText([spec.title, ...spec.tags, ...criteria].join('\n')));
|
|
@@ -205,13 +259,13 @@ export function checkDoneEvidenceGate(spec, criteria, artifacts) {
|
|
|
205
259
|
}
|
|
206
260
|
return issues;
|
|
207
261
|
}
|
|
208
|
-
export function checkLifecycleEvidenceGate(args) {
|
|
262
|
+
export async function checkLifecycleEvidenceGate(args) {
|
|
209
263
|
const issues = args.artifacts.invalidArtifacts.map((artifact) => ({
|
|
210
264
|
code: 'evidence_artifact_invalid',
|
|
211
265
|
message: artifact,
|
|
212
266
|
}));
|
|
213
267
|
if (args.receiptContext) {
|
|
214
|
-
issues.push(...provenanceIssues(args.artifacts, args.receiptContext));
|
|
268
|
+
issues.push(...(await provenanceIssues(args.artifacts, args.receiptContext)));
|
|
215
269
|
}
|
|
216
270
|
if (args.transition === 'approved') {
|
|
217
271
|
issues.push(...checkDiscoveryGate(args.spec, args.artifacts));
|
|
@@ -4,6 +4,13 @@ export type { DurableValidationBindings } from '../../types/durable-validation.j
|
|
|
4
4
|
export { gitSourceTreeDigest } from './validation-source-digest.js';
|
|
5
5
|
export { createPendingValidationSubmission, createValidationSubmission, validationOperationId, validationSubmissionOperationId, } from './validation-submission.js';
|
|
6
6
|
export declare function toValidationReceiptBindings(bindings: DurableValidationBindings, artifactDigests: ValidationArtifactDigests): ValidationReceiptBindings;
|
|
7
|
+
/**
|
|
8
|
+
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
9
|
+
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
10
|
+
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
11
|
+
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
12
|
+
*/
|
|
13
|
+
export declare function doneReceiptBindingsDifferOnlyInEvidence(observed: ValidationReceiptBindings | undefined, stored: ValidationReceiptBindings | undefined): boolean;
|
|
7
14
|
/** Compute immutable validation and lifecycle-artifact bindings without trusting spec metadata. */
|
|
8
15
|
export declare function computeDurableValidationBindings(input: {
|
|
9
16
|
readonly canonicalProjectId: string;
|
|
@@ -46,6 +46,31 @@ export function toValidationReceiptBindings(bindings, artifactDigests) {
|
|
|
46
46
|
artifactDigests,
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
51
|
+
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
52
|
+
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
53
|
+
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
54
|
+
*/
|
|
55
|
+
export function doneReceiptBindingsDifferOnlyInEvidence(observed, stored) {
|
|
56
|
+
if (!observed?.artifactDigests || !stored?.artifactDigests) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const codeBindingsMatch = observed.projectId === stored.projectId &&
|
|
60
|
+
observed.specId === stored.specId &&
|
|
61
|
+
observed.sourceDigest === stored.sourceDigest &&
|
|
62
|
+
observed.specDigest === stored.specDigest &&
|
|
63
|
+
observed.policyDigest === stored.policyDigest &&
|
|
64
|
+
observed.toolchainDigest === stored.toolchainDigest &&
|
|
65
|
+
observed.artifactDigests.approvedSpec === stored.artifactDigests.approvedSpec;
|
|
66
|
+
if (!codeBindingsMatch) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return (observed.artifactDigests.discovery !== stored.artifactDigests.discovery ||
|
|
70
|
+
observed.artifactDigests.taskPlan !== stored.artifactDigests.taskPlan ||
|
|
71
|
+
observed.artifactDigests.traceability !== stored.artifactDigests.traceability ||
|
|
72
|
+
observed.artifactDigests.contractValidation !== stored.artifactDigests.contractValidation);
|
|
73
|
+
}
|
|
49
74
|
async function digestFile(path, missingLabel) {
|
|
50
75
|
try {
|
|
51
76
|
return digest(await readFile(path));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DurableJobRecord } from '../../storage/runtime-db.js';
|
|
2
2
|
import { type ValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
|
|
3
|
-
|
|
3
|
+
export { doneReceiptBindingsDifferOnlyInEvidence } from '../../engine/validation/durable-validation.js';
|
|
4
4
|
export type DoneReceiptVerifier = (identity: {
|
|
5
5
|
projectId: string;
|
|
6
6
|
specId: string;
|
|
@@ -12,12 +12,5 @@ export type DoneReceiptVerifier = (identity: {
|
|
|
12
12
|
freshnessLease?: ValidationFreshnessLease;
|
|
13
13
|
}>;
|
|
14
14
|
export declare function completedJobPublishesReceipt(job: DurableJobRecord | undefined, receiptId: string): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
17
|
-
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
18
|
-
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
19
|
-
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
20
|
-
*/
|
|
21
|
-
export declare function doneReceiptBindingsDifferOnlyInEvidence(observed: ValidationReceiptBindings | undefined, stored: ValidationReceiptBindings | undefined): boolean;
|
|
22
15
|
export declare function verifyCurrentDoneReceipt(identity: Parameters<DoneReceiptVerifier>[0]): ReturnType<DoneReceiptVerifier>;
|
|
23
16
|
//# sourceMappingURL=done-receipt-verifier.d.ts.map
|
|
@@ -57,7 +57,8 @@ import { projectDataDir } from '../../storage/base-store.js';
|
|
|
57
57
|
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
58
58
|
import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidationReceiptAuthority, readCurrentValidationReceipt, } from '../../engine/validation/validation-receipt.js';
|
|
59
59
|
import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, closeValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
|
|
60
|
-
import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
60
|
+
import { computeDurableValidationBindings, doneReceiptBindingsDifferOnlyInEvidence, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
61
|
+
export { doneReceiptBindingsDifferOnlyInEvidence } from '../../engine/validation/durable-validation.js';
|
|
61
62
|
export function completedJobPublishesReceipt(job, receiptId) {
|
|
62
63
|
if (job?.state !== 'completed' || !job.result || typeof job.result !== 'object') {
|
|
63
64
|
return false;
|
|
@@ -70,31 +71,6 @@ export function completedJobPublishesReceipt(job, receiptId) {
|
|
|
70
71
|
return (published.receiptId === receiptId &&
|
|
71
72
|
published.receiptKey === `validation-receipt:${receiptId.slice('sha256:'.length)}`);
|
|
72
73
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
75
|
-
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
76
|
-
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
77
|
-
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
78
|
-
*/
|
|
79
|
-
export function doneReceiptBindingsDifferOnlyInEvidence(observed, stored) {
|
|
80
|
-
if (!observed?.artifactDigests || !stored?.artifactDigests) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
const codeBindingsMatch = observed.projectId === stored.projectId &&
|
|
84
|
-
observed.specId === stored.specId &&
|
|
85
|
-
observed.sourceDigest === stored.sourceDigest &&
|
|
86
|
-
observed.specDigest === stored.specDigest &&
|
|
87
|
-
observed.policyDigest === stored.policyDigest &&
|
|
88
|
-
observed.toolchainDigest === stored.toolchainDigest &&
|
|
89
|
-
observed.artifactDigests.approvedSpec === stored.artifactDigests.approvedSpec;
|
|
90
|
-
if (!codeBindingsMatch) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return (observed.artifactDigests.discovery !== stored.artifactDigests.discovery ||
|
|
94
|
-
observed.artifactDigests.taskPlan !== stored.artifactDigests.taskPlan ||
|
|
95
|
-
observed.artifactDigests.traceability !== stored.artifactDigests.traceability ||
|
|
96
|
-
observed.artifactDigests.contractValidation !== stored.artifactDigests.contractValidation);
|
|
97
|
-
}
|
|
98
74
|
export async function verifyCurrentDoneReceipt(identity) {
|
|
99
75
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
100
76
|
try {
|
|
@@ -117,11 +93,20 @@ export async function verifyCurrentDoneReceipt(identity) {
|
|
|
117
93
|
projectId: identity.projectId,
|
|
118
94
|
issuerId: LOCAL_VALIDATION_RECEIPT_ISSUER,
|
|
119
95
|
});
|
|
120
|
-
|
|
96
|
+
let verification = await authority.verifyCurrent({
|
|
121
97
|
...currentBindings,
|
|
122
98
|
projectId: identity.projectId,
|
|
123
99
|
specId: identity.specId,
|
|
124
100
|
});
|
|
101
|
+
const evidenceOnlyDrift = verification.reason === 'stale-bindings' &&
|
|
102
|
+
doneReceiptBindingsDifferOnlyInEvidence(currentBindings, receipt.bindings);
|
|
103
|
+
if (evidenceOnlyDrift) {
|
|
104
|
+
verification = await authority.verifyCurrent({
|
|
105
|
+
...receipt.bindings,
|
|
106
|
+
projectId: identity.projectId,
|
|
107
|
+
specId: identity.specId,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
125
110
|
if (!verification.valid) {
|
|
126
111
|
return verification;
|
|
127
112
|
}
|
|
@@ -134,12 +134,20 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
134
134
|
requiredContractKinds: [],
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
const result = checkLifecycleEvidenceGate({
|
|
137
|
+
const result = await checkLifecycleEvidenceGate({
|
|
138
138
|
transition: args.transition,
|
|
139
139
|
spec: args.spec,
|
|
140
140
|
criteria,
|
|
141
141
|
artifacts,
|
|
142
|
-
...(receiptResult?.ok
|
|
142
|
+
...(receiptResult?.ok
|
|
143
|
+
? {
|
|
144
|
+
receiptContext: {
|
|
145
|
+
receipt: receiptResult.receipt,
|
|
146
|
+
...(args.projectPath === undefined ? {} : { projectPath: args.projectPath }),
|
|
147
|
+
specPath: args.spec.specPath,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
143
151
|
});
|
|
144
152
|
if (args.transition === 'done') {
|
|
145
153
|
const index = await buildSpecEvidenceIndex({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ValidationArtifactProvenance, ValidationReceipt } from './validation-receipt.js';
|
|
1
|
+
import type { ValidationArtifactDigests, ValidationArtifactProvenance, ValidationReceipt } from './validation-receipt.js';
|
|
2
2
|
import type { ValidationGateCapability } from './validation-evidence.js';
|
|
3
3
|
export type EvidenceGateTransition = 'approved' | 'implementing' | 'done';
|
|
4
4
|
export interface DiscoveryEvidence {
|
|
@@ -68,6 +68,16 @@ export interface EvidenceArtifacts {
|
|
|
68
68
|
}
|
|
69
69
|
export interface EvidenceReceiptContext {
|
|
70
70
|
receipt: ValidationReceipt;
|
|
71
|
+
projectPath?: string;
|
|
72
|
+
specPath?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface ProvenanceCandidate {
|
|
75
|
+
label: string;
|
|
76
|
+
artifact: {
|
|
77
|
+
provenance?: ValidationArtifactProvenance;
|
|
78
|
+
};
|
|
79
|
+
digestKey: keyof ValidationArtifactDigests;
|
|
80
|
+
actualDigest?: string;
|
|
71
81
|
}
|
|
72
82
|
export interface EvidenceGateIssue {
|
|
73
83
|
code: 'discovery_missing' | 'discovery_unresolved_questions' | 'task_plan_missing' | 'task_plan_uncovered_criteria' | 'traceability_missing' | 'traceability_uncovered_criteria' | 'traceability_incomplete_rows' | 'contract_validation_missing' | 'contract_validation_failed' | 'evidence_provenance_invalid' | 'done_drift_uncovered_criteria' | 'done_drift_stale_evidence' | 'done_drift_unapproved_scope' | 'evidence_artifact_invalid' | 'SUPERSEDED_RECONCILIATION_EVIDENCE';
|
package/package.json
CHANGED
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.
|
|
5
|
+
"version": "5.3.31",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": ["npx", "@planu/cli@latest"],
|
|
8
8
|
"packageName": "@planu/cli",
|