aimship 0.1.0-preview.20 → 0.1.0-preview.21
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/CONTRIBUTING.md +3 -3
- package/README.md +6 -4
- package/README.zh-CN.md +6 -4
- package/docs/cli.md +36 -13
- package/docs/examples.md +4 -0
- package/docs/guide.md +15 -11
- package/docs/guide.zh-CN.md +15 -11
- package/lib/approvals.mjs +47 -126
- package/lib/cli.mjs +10 -8
- package/lib/delivery.mjs +23 -38
- package/lib/documents.mjs +7 -6
- package/lib/domains.mjs +123 -0
- package/lib/execution.mjs +4 -14
- package/lib/language.mjs +2 -1
- package/lib/operations.mjs +30 -0
- package/lib/projections.mjs +73 -0
- package/lib/protocol.mjs +5 -5
- package/lib/state.mjs +30 -11
- package/lib/view.mjs +136 -72
- package/package.json +2 -2
- package/skills/aimship/SKILL.md +1 -1
- package/skills/aimship/references/core.md +19 -5
- package/skills/aimship/references/document-patterns.md +15 -1
- package/skills/aimship/references/document-review.md +2 -2
- package/skills/aimship/references/recovery.md +4 -4
- package/skills/aimship/references/review-method.md +2 -2
- package/skills/aimship/references/reviews/batch.md +2 -2
- package/skills/aimship/references/reviews/overall.md +3 -3
- package/skills/aimship/references/reviews/recheck.md +1 -1
- package/skills/aimship/references/routing.md +1 -1
- package/skills/aimship/references/sources-and-changes.md +1 -1
- package/skills/aimship/references/stages/contract.md +2 -2
- package/skills/aimship/references/stages/diagnose.md +1 -1
- package/skills/aimship/references/stages/implement.md +4 -4
- package/skills/aimship/references/stages/plan.md +1 -1
- package/skills/aimship/references/stages/review.md +1 -1
- package/skills/aimship/references/stages/tech-design.md +3 -3
- package/skills/aimship/references/stages/test-design.md +1 -1
- package/skills/aimship/references/stages/verify.md +3 -3
- package/skills/aimship/references/templates/plan.md +7 -1
- package/skills/aimship/references/templates/review.md +7 -2
- package/skills/aimship/references/templates/testing.md +8 -2
package/lib/approvals.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
import { artifactMap, safeRead, hasLocator, sections } from './documents.mjs';
|
|
2
|
+
import { artifactMap, safeRead, hasLocator, sections, definitions } from './documents.mjs';
|
|
3
3
|
import { fail, hash } from './io.mjs';
|
|
4
|
+
import {evaluateRuns} from './domains.mjs';
|
|
4
5
|
|
|
5
6
|
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
6
7
|
const reject = message => fail('REVIEW_CHECK', message, 'Read review status. Preserve history, self-check the current document, and record only an actual explicit user decision.');
|
|
@@ -33,7 +34,7 @@ export async function reviewFingerprint(directory, state, doc) {
|
|
|
33
34
|
}
|
|
34
35
|
const assets = [];
|
|
35
36
|
for (const ref of doc.assetRefs ?? []) { const bytes = await safeRead(directory,state,ref,null); if (bytes === null) return null; assets.push([ref,hash(bytes)]); }
|
|
36
|
-
return hash(assets.length ? JSON.stringify([hash(body),assets]) : body);
|
|
37
|
+
return hash(assets.length||doc.coverageRefs ? JSON.stringify([hash(body),assets,doc.coverageRefs??null]) : body);
|
|
37
38
|
}
|
|
38
39
|
function decisionFor(state, doc, digest) {
|
|
39
40
|
let approved = null, checked = null, latest = null;
|
|
@@ -49,49 +50,6 @@ function decisionFor(state, doc, digest) {
|
|
|
49
50
|
if (approved?.version !== doc.version || approved?.digest !== digest) approved = null;
|
|
50
51
|
return { approved, checked, latest };
|
|
51
52
|
}
|
|
52
|
-
// A corrective review may consume a failed conclusion without first pretending
|
|
53
|
-
// it passed. Its recorded coverage must include the object it supersedes.
|
|
54
|
-
function reviewsResult(later, earlier) {
|
|
55
|
-
return later.stage === 'review' && earlier.stage === 'review' && text(earlier.resultRef)
|
|
56
|
-
&& later.historyResultRefs?.includes(earlier.resultRef)
|
|
57
|
-
&& (!earlier.overall || later.overall === true)
|
|
58
|
-
&& (earlier.reviewOf?.length
|
|
59
|
-
? earlier.reviewOf.every(id => later.reviewOf?.includes(id))
|
|
60
|
-
: !later.reviewOf?.length && later.scope === earlier.scope);
|
|
61
|
-
}
|
|
62
|
-
// Required edges for standard delivery are derived from the actual work, not
|
|
63
|
-
// optional inputRefs/dependsOn. Limited commissions retain their selected graph.
|
|
64
|
-
function standardEntry(state, run, index, completed) {
|
|
65
|
-
const before=state.stageRuns.slice(0,index), dependsOn=[], issues=[];
|
|
66
|
-
const status=id=>completed.find(r=>r.id===id);
|
|
67
|
-
const implementations=before.filter(r=>r.stage==='implement');
|
|
68
|
-
if(run.stage==='review') {
|
|
69
|
-
const covered=run.reviewOf??[];
|
|
70
|
-
if(!covered.length) issues.push('entry:implementation-coverage');
|
|
71
|
-
dependsOn.push(...covered);
|
|
72
|
-
if(run.overall && implementations.some(r=>!covered.includes(r.id))) issues.push('entry:overall-coverage');
|
|
73
|
-
for(const id of covered) {
|
|
74
|
-
const implementation=state.stageRuns.find(r=>r.id===id);
|
|
75
|
-
if(['failed','mixed','unknown','not-run'].includes(implementation?.result)) issues.push(`entry:implementation-result:${id}`);
|
|
76
|
-
}
|
|
77
|
-
} else if(['verify','handoff'].includes(run.stage)) {
|
|
78
|
-
if(!implementations.length) issues.push('entry:implementation');
|
|
79
|
-
dependsOn.push(...implementations.map(r=>r.id),...before.filter(r=>r.stage==='review').map(r=>r.id));
|
|
80
|
-
const overall=before.findLast(r=>r.stage==='review' && r.overall && r.result==='passed' && status(r.id)?.releaseReady && implementations.length && implementations.every(b=>r.reviewOf?.includes(b.id) && before.indexOf(b)<before.indexOf(r)));
|
|
81
|
-
if(!overall) issues.push('entry:passing-overall-review');
|
|
82
|
-
if(run.stage==='handoff') {
|
|
83
|
-
const checks=before.filter(r=>r.stage==='verify');
|
|
84
|
-
const effective=checks.filter((r,i)=>!checks.slice(i+1).some(later=>status(later.id)?.releaseReady && later.result==='passed' && later.historyResultRefs?.includes(r.resultRef)));
|
|
85
|
-
if(!effective.length) issues.push('entry:verification');
|
|
86
|
-
dependsOn.push(...checks.map(r=>r.id));
|
|
87
|
-
for(const check of effective) {
|
|
88
|
-
if(check.result!=='passed') issues.push(`entry:verification-result:${check.id}`);
|
|
89
|
-
if(overall && before.indexOf(check)<before.indexOf(overall)) issues.push(`entry:verification-before-review:${check.id}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return {dependsOn,issues};
|
|
94
|
-
}
|
|
95
53
|
export async function reviewStatus(directory, state) {
|
|
96
54
|
const map = await artifactMap(), documents = [], evidenceIssues = [], evidenceCache = new Map();
|
|
97
55
|
for (const r of state.reviewHistory ?? []) {
|
|
@@ -113,64 +71,16 @@ export async function reviewStatus(directory, state) {
|
|
|
113
71
|
try { await evidence(directory,state,state.deliveryPolicy.partialRef); limitedScope=true; }
|
|
114
72
|
catch { /* Report an unavailable scope basis; never silently grant an exception. */ }
|
|
115
73
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const kind = spec.kind === 'requirement' ? requirement : spec.kind;
|
|
122
|
-
required = (state.documents ?? []).filter(d => d.kind === kind || (run.stage === 'contract' && d.kind === 'sql')).map(d => d.path);
|
|
123
|
-
}
|
|
124
|
-
const declared = run.dependsOn ?? (i ? [state.stageRuns[i - 1].id] : []);
|
|
125
|
-
const producers = state.stageRuns.slice(0, i).filter(r => r.outputRefs.some(out => run.inputRefs.some(input => out.split('#')[0] === input.split('#')[0]))).map(r => r.id);
|
|
126
|
-
const development = ['requirements','tech-design','contract','test-design','plan','implement'];
|
|
127
|
-
const prerequisites = development.includes(run.stage) ? state.stageRuns.slice(0,i).filter(r=>development.includes(r.stage) && development.indexOf(r.stage)<development.indexOf(run.stage)).map(r=>r.id) : [];
|
|
128
|
-
const gated=state.deliveryPolicy?.version===1 && ['review','verify','handoff'].includes(run.stage);
|
|
129
|
-
const entrance=gated && !limitedScope ? standardEntry(state,run,i,runs) : {dependsOn:[],issues:[]};
|
|
130
|
-
if(gated && state.deliveryPolicy.partialRef && !limitedScope) entrance.issues.push('entry:limited-scope-evidence');
|
|
131
|
-
const dependsOn = [...new Set([...declared, ...producers, ...prerequisites,...entrance.dependsOn])];
|
|
132
|
-
const blockedBy = dependsOn.filter(id => {
|
|
133
|
-
const dependency = runs.find(r => r.id === id);
|
|
134
|
-
const original = state.stageRuns.find(r => r.id === id);
|
|
135
|
-
return !dependency?.releaseReady && !(dependency?.ready && reviewsResult(run, original));
|
|
136
|
-
});
|
|
137
|
-
blockedBy.push(...entrance.issues);
|
|
138
|
-
// An omitted producer run must not turn an unapproved input into permission.
|
|
139
|
-
// Schema 4 keeps prerequisite approvals even when producer runs are omitted.
|
|
140
|
-
if (state.schemaVersion === 4) {
|
|
141
|
-
const needs = [];
|
|
142
|
-
if (['prototype','tech-design','contract','test-design','plan','implement'].includes(run.stage)) needs.push(requirement);
|
|
143
|
-
if (['contract','test-design','plan','implement'].includes(run.stage)) needs.push('tech-design');
|
|
144
|
-
if (['plan','implement'].includes(run.stage) && !state.deliveryPolicy?.partialRef) needs.push('testing');
|
|
145
|
-
if (run.stage === 'implement') needs.push('plan');
|
|
146
|
-
if (['test-design','plan','implement'].includes(run.stage)) {
|
|
147
|
-
if (state.documentPolicy.interface !== null && state.documentPolicy.interface !== 'none') needs.push('contract');
|
|
148
|
-
if (state.documentPolicy.databases?.length) needs.push('sql');
|
|
149
|
-
}
|
|
150
|
-
if (run.stage === 'implement' && state.stageRuns.some(r=>r.stage==='prototype')) needs.push('prototype');
|
|
151
|
-
for (const kind of needs) {
|
|
152
|
-
const inputs = (state.documents ?? []).filter(d=>d.kind === kind);
|
|
153
|
-
if (!inputs.length || inputs.some(d=>!['approved','adopted'].includes(documents.find(r=>r.path===d.path)?.status))) blockedBy.push(`approval:${kind}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const waiting = required.filter(file => !['approved','adopted'].includes(documents.find(d => d.path === file)?.status));
|
|
157
|
-
const ready = run.progress === 'done' && !['needs-review','unknown'].includes(run.applicability) && !blockedBy.length && !waiting.length && (!spec?.humanReview || required.length > 0);
|
|
158
|
-
const releaseReady = ready && (run.stage !== 'review' || run.result === 'passed');
|
|
159
|
-
runs.push({ id: run.id, stage: run.stage, progress: run.progress, applicability: run.applicability, documents: required, waiting, dependsOn, blockedBy, ready, releaseReady });
|
|
160
|
-
if (releaseReady && run.stage === 'review') {
|
|
161
|
-
for (const earlier of runs.slice(0,-1)) {
|
|
162
|
-
if (earlier.ready && reviewsResult(run, state.stageRuns.find(r=>r.id===earlier.id))) {
|
|
163
|
-
earlier.releaseReady = true;
|
|
164
|
-
earlier.resolvedBy = run.id;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
74
|
+
let expectedCoverage=[];
|
|
75
|
+
const testing=state.documents.find(d=>d.kind==='testing');
|
|
76
|
+
if(testing){const body=await safeRead(directory,state,testing.path).catch(()=>null);expectedCoverage=testing.mode==='new'&&body?definitions(body,testing.path,map.documents.testing).filter(d=>d.id.startsWith('TC-')).map(d=>testing.path+'#'+d.id):(testing.coverageRefs??[]);}
|
|
77
|
+
const execution=evaluateRuns(state,documents,map,{limitedScope,expectedCoverage,evidenceIssues});
|
|
78
|
+
const runs=execution.runs;
|
|
169
79
|
const current = runs.find(r => r.id === state.currentRun);
|
|
170
80
|
const pending = runs.filter(r => r.progress !== 'planned').flatMap(r => r.waiting.map(document => ({ run: r.id, document, status: documents.find(d => d.path === document)?.status })));
|
|
171
81
|
const nextRun = runs.find(r => !r.ready);
|
|
172
82
|
const nextStage = current ? runs[runs.indexOf(current) + 1]?.stage ?? null : nextRun?.stage ?? null;
|
|
173
|
-
return { compatibility: 'current', documents, runs, pending, evidenceIssues, currentRun: current?.id ?? null, nextRun: nextRun?.id ?? null, nextStage, canProceed: !state.next.questionRef && (current ? !current.blockedBy.length && !(current.progress === 'done' && !current.releaseReady) && current.progress !== 'blocked' && !['needs-review','unknown'].includes(current.applicability) : !pending.length && !runs.some(r=>r.progress==='blocked' || (r.progress === 'done' && !r.releaseReady) || (r.progress !== 'planned' && r.blockedBy.length) || ['needs-review','unknown'].includes(r.applicability))), identity: 'Record validation only; user identity, intent and source authenticity are not authenticated.' };
|
|
83
|
+
return { compatibility: 'current', execution, documents, runs, pending, evidenceIssues, currentRun: current?.id ?? null, nextRun: nextRun?.id ?? null, nextStage, canProceed: !state.next.questionRef && (current ? !current.blockedBy.length && !(current.progress === 'done' && !current.releaseReady) && current.progress !== 'blocked' && !['needs-review','unknown'].includes(current.applicability) : !pending.length && !runs.some(r=>r.progress==='blocked' || (r.progress === 'done' && !r.releaseReady) || (r.progress !== 'planned' && r.blockedBy.length) || ['needs-review','unknown'].includes(r.applicability))), identity: 'Record validation only; user identity, intent and source authenticity are not authenticated.' };
|
|
174
84
|
}
|
|
175
85
|
async function evidence(directory, state, ref) {
|
|
176
86
|
if (!text(ref) || ref.includes('\\') || ref.split('#')[0].split('/').includes('..') || ref.startsWith('/') || (/^[a-zA-Z]:/.test(ref) && !ref.startsWith('repo:'))) reject('Review evidence must stay inside a registered root.');
|
|
@@ -214,34 +124,45 @@ export async function enforceReviews(directory, state, previous) {
|
|
|
214
124
|
const a = first(order[i]), b = first(order[j]);
|
|
215
125
|
if (a >= 0 && b >= 0 && a > b && !previous?.stageRuns.some(r => r.id === state.stageRuns[b].id && r.progress !== 'planned')) reject(`${order[i]} must precede ${order[j]} in the initial development arrangement.`);
|
|
216
126
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
if
|
|
230
|
-
if
|
|
231
|
-
if (
|
|
232
|
-
for (const r of status.runs.filter(r => r.documents.includes(doc.path) && r.progress !== 'planned')) affected.add(r.id);
|
|
127
|
+
let status=await reviewStatus(directory,state);
|
|
128
|
+
const addedChanges=state.openChanges.filter(c=>!previous?.openChanges.some(old=>JSON.stringify(old)===JSON.stringify(c)));
|
|
129
|
+
const affected=new Set();
|
|
130
|
+
for(const doc of state.documents){
|
|
131
|
+
const priorDoc=previous?.documents.find(d=>d.path===doc.path),oldApproval=oldHistory.findLast(r=>r.document===doc.path&&(['approved','editorial'].includes(r.kind)||(priorDoc?.mode==='input'&&r.kind==='self-check'&&r.result==='passed')));
|
|
132
|
+
const now=status.documents.find(d=>d.path===doc.path);if(!priorDoc||!oldApproval||!now)continue;
|
|
133
|
+
const changed=oldApproval.digest!==now.digest||oldApproval.version!==doc.version;
|
|
134
|
+
const previousContentChanged=previous.documentDigests?.[doc.path]!==state.documentDigests?.[doc.path]||priorDoc.version!==doc.version;
|
|
135
|
+
const editorial=state.reviewHistory.slice(oldHistory.length).some(r=>r.document===doc.path&&r.kind==='editorial');
|
|
136
|
+
if(!changed||editorial)continue;
|
|
137
|
+
const change=(previousContentChanged?addedChanges:state.openChanges).find(c=>c.impactRef&&Array.isArray(c.affectedRefs));
|
|
138
|
+
if(!change)reject(`${doc.path}: record the material change and scoped impact, or reconcile a true editorial change.`);
|
|
139
|
+
if(['approved','adopted'].includes(now.status)&&doc.version===oldApproval.version)reject(`${doc.path}: material content needs a new version.`);
|
|
140
|
+
if(!previousContentChanged)continue;
|
|
141
|
+
for(const r of state.stageRuns){if(r.stage==='verify'){const refs=(r.coverageRefs??[]).filter(ref=>change.affectedRefs.includes(ref)||change.affectedRefs.includes(doc.path));r.invalidCoverageRefs=[...new Set([...(r.invalidCoverageRefs??[]),...refs])];}if(r.progress!=='planned'&&(r.outputRefs.includes(doc.path)&&!['implement','review','verify'].includes(r.stage)||change.affectedRefs.some(ref=>r.inputRefs.includes(ref)||r.outputRefs.includes(ref)||r.resultRef===ref||state.batches.some(b=>b.id===r.batchId&&(b.definitionRef===ref||b.taskRefs.includes(ref))))))affected.add(r.id);}
|
|
233
142
|
}
|
|
234
|
-
|
|
235
|
-
for
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
143
|
+
// A new authority or an explicit scoped impact need not edit the same file.
|
|
144
|
+
for(const change of addedChanges){const old=previous?.openChanges.find(c=>c.id===change.id);if(!change.impactRef||!change.affectedRefs||old&&old.impactRef===change.impactRef&&JSON.stringify(old.affectedRefs)===JSON.stringify(change.affectedRefs))continue;
|
|
145
|
+
for(const r of state.stageRuns){if(r.progress==='planned')continue;const refs=change.affectedRefs;if(r.stage==='verify'){const invalid=(r.coverageRefs??[]).filter(ref=>refs.includes(ref)||refs.includes(ref.split('#')[0]));r.invalidCoverageRefs=[...new Set([...(r.invalidCoverageRefs??[]),...invalid])];}
|
|
146
|
+
if(refs.some(ref=>r.inputRefs.includes(ref)||r.outputRefs.includes(ref)||r.resultRef===ref||state.batches.some(b=>b.id===r.batchId&&(b.definitionRef===ref||b.taskRefs.includes(ref)))))affected.add(r.id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
let changed=true;while(changed){const size=affected.size,affectedB=new Set(state.stageRuns.filter(r=>affected.has(r.id)&&r.batchId).map(r=>r.batchId));for(const b of state.batches)if(b.dependsOn.some(id=>affectedB.has(id)))for(const r of state.stageRuns.filter(r=>r.batchId===b.id&&r.progress!=='planned'))affected.add(r.id);for(const r of state.stageRuns)if([...(r.dependsOn??[]),...(r.reviewOf??[])].some(id=>affected.has(id)))affected.add(r.id);changed=affected.size!==size;}
|
|
150
|
+
for(const r of state.stageRuns)if(affected.has(r.id))r.applicability='needs-review';
|
|
151
|
+
status=await reviewStatus(directory,state);
|
|
152
|
+
for(const r of state.stageRuns)if(r.applicability==='needs-review'&&!['implement','review','verify','handoff'].includes(r.stage)&&state.reviewHistory.slice(oldHistory.length).some(h=>r.outputRefs.includes(h.document)&&['approved','editorial','self-check'].includes(h.kind))&&r.outputRefs.length&&r.outputRefs.every(ref=>['approved','adopted'].includes(status.documents.find(d=>d.path===ref)?.status)))r.applicability='current';
|
|
153
|
+
status=await reviewStatus(directory,state);
|
|
154
|
+
for(const run of state.stageRuns){
|
|
155
|
+
const old=previous?.stageRuns.find(r=>r.id===run.id);
|
|
156
|
+
const progressing=['active','done'].includes(run.progress)&&(!old||old.progress!==run.progress||old.resultRef!==run.resultRef||old.applicability==='needs-review'&&run.applicability==='current');
|
|
157
|
+
const recovering=old&&['needs-review','unknown'].includes(old.applicability)&&run.applicability==='current';
|
|
158
|
+
const currentEntry=recovering?await reviewStatus(directory,{...state,stageRuns:state.stageRuns.map(r=>r.id===run.id?{...r,approvalBasis:undefined}:r)}):status;
|
|
159
|
+
if((progressing||recovering)&¤tEntry.runs.find(r=>r.id===run.id).blockedBy.length)reject(`${run.id}: prerequisite completion, approval or affected work is unresolved.`);
|
|
160
|
+
const rs=status.runs.find(r=>r.id===run.id);
|
|
161
|
+
if(run.progress==='done'&&rs.ready&&!run.approvalBasis?.length){
|
|
162
|
+
const relevant=status.documents.filter(d=>['approved','adopted'].includes(d.status));
|
|
163
|
+
run.approvalBasis=relevant.map(d=>{const event=state.reviewHistory.findLast(h=>h.document===d.path&&h.digest===d.digest&&['approved','editorial','self-check'].includes(h.kind));return {document:d.path,digest:d.digest,recordId:event.id};});
|
|
164
|
+
}
|
|
244
165
|
}
|
|
245
|
-
if (state.outcome.goal === 'met' && previous?.outcome.goal !== 'met' && status.runs.some(r => !r.ready)) reject('Resolve selected stages and document approvals before declaring the goal met.');
|
|
166
|
+
if (state.outcome.goal === 'met' && previous?.outcome.goal !== 'met' && status.runs.some(r => !r.ready&&!r.resolvedBy&&r.stage!=='verify')) reject('Resolve selected stages and document approvals before declaring the goal met.');
|
|
246
167
|
return status;
|
|
247
168
|
}
|
package/lib/cli.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const HELP = {
|
|
|
19
19
|
bind: 'Resolve active installation once. Returns immutable release/core/stage/CLI paths plus configured dataRoot, configPath and customPath. Source mode returns the default data root with null config/custom paths. No directories are created. Optional --home is for isolated testing.',
|
|
20
20
|
init: 'init <dir> --input <json-file|-> --rules-version <version>. Create state once; input requires goal.summary, language (en or zh-CN) and saved .aimship/init.md; directory must be a child of bound dataRoot unless storage has an explicit local decisionRef; endpoint defaults to standard delivery; artifact conditions may remain null (unknown). Does not create templates.',
|
|
21
21
|
read: 'read <dir> [--rules-version <version>] [--delivery-id <known-id>]. Read current delivery state without executing work.',
|
|
22
|
-
update: 'update <dir> --revision <N> --rules-version <version> --input <json-file|->.
|
|
22
|
+
update: 'update <dir> --revision <N> --rules-version <version> --input <json-file|->. Merge ordinary metadata; record controlled facts with operations. Identity/history fields are managed. null is a literal value, not deletion.',
|
|
23
23
|
check: 'check <dir> [--rules-version <version>] [--all]. Check structure and current input references (or every registered reference). Checks ordinary Markdown sections and searchable numbers in new local Markdown; reused and unsupported fragments are explicitly not checked. Does not prove semantics, remote access or validation results.',
|
|
24
24
|
resume: 'resume <dir> [--rules-version <version>] [--delivery-id <known-id>]. Read state and return fixed rule paths, required inputs, warnings, and continuation text; never execute work or rewrite state.',
|
|
25
25
|
recover: 'recover <dir> --revision <backup-revision> --rules-version <version>. Restore state.previous.json only over missing/corrupt state; preserve corrupted bytes. Inspect actual artifacts afterward; nothing reruns.',
|
|
@@ -32,8 +32,9 @@ const HELP = {
|
|
|
32
32
|
protocolVersion: PROTOCOL_VERSION,
|
|
33
33
|
documentReview: {reviewPolicy:1, reviewHistory:'Append-only {id,kind,document,version,digest,sourceRef,summary}. self-check requires result: passed|failed; approved requires decision: explicit-approval; editorial requires previousDigest. Read review.documents for current digests. Tools validate records, not user identity. See docs/cli.md#document-review-protocol.'},
|
|
34
34
|
deliveryPolicy: 'New states default to {version:1}; partialRef identifies an explicitly limited scope, null restores standard scope with a new goal decision. Completion is derived separately from goal/acceptance.',
|
|
35
|
-
implementationWork: '
|
|
36
|
-
|
|
35
|
+
implementationWork: 'Started implementation requires workType batch|repair; business work has registered batchId. Repairs retain B identity and prior result references. Completed implement/review/verify records require objectKey SHA-256 for the actual object. reviewScope is batch|overall; reviewOf covers actual work. Formal verify declares coverageRefs and optional per-coverage results; historyResultRefs links rechecks/retests.',
|
|
36
|
+
operations: ['register-batches','record-run','remove-planned-run','record-review','record-change','record-outcome','set-current','set-applicability'],
|
|
37
|
+
sessions: 'One batch includes automatic batch review and authorized repair/recheck before stopping. After the last batch recommend a fresh session for the existing overall review, unless explicitly authorized here. Overall review stops before formal verification. Host delegation is conditional and adds no stage or business batch. Session advice executes nothing and grants no approval.',
|
|
37
38
|
stages: STAGES,
|
|
38
39
|
progress: ['planned', 'active', 'blocked', 'done'],
|
|
39
40
|
result: ['passed', 'failed', 'not-run', 'unknown', 'mixed'],
|
|
@@ -42,15 +43,16 @@ const HELP = {
|
|
|
42
43
|
stageInstance: { id: 'stable-instance-id', stage: '<stage>', scope: 'batch or whole-delivery scope', progress: 'planned', inputRefs: [], outputRefs: [], resultRef: 'review.md#REV-001 (required when done)' },
|
|
43
44
|
references: 'delivery-relative/path#optional-fragment, repo:<registered-id>/<repo-relative-path>#fragment, or HTTP(S) URL. Local paths cannot escape their root. Repository registration: {id, path: absolute-path, role}.',
|
|
44
45
|
topLevel: {
|
|
45
|
-
managed: 'schemaVersion, protocolVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, documentDigests, writeResult. Do not supply these in init/update. rulesVersion records the last saving tool; protocolVersion identifies compatible delivery semantics. Legacy 17/18 without it map precisely to protocol 1. Reads never adopt metadata;
|
|
46
|
+
managed: 'schemaVersion, protocolVersion, revision, deliveryId, rulesVersion, createdAt, updatedAt, goalHistory, documentDigests, writeResult. Do not supply these in init/update. rulesVersion records the last saving tool; protocolVersion identifies compatible delivery semantics. Legacy 17/18 without it map precisely to protocol 1. Reads never adopt metadata; protocol 2 does not continue schema4/protocol1 deliveries. No migration or version-field rewrite.',
|
|
46
47
|
requiredInit: 'language: en|zh-CN and goal: {summary, endpoint?}; save nonempty initialization notes in .aimship/init.md before init. Defaults: endpoint standard delivery, goalRef .aimship/init.md, empty documents, and null documentPolicy conditions/authority. Register PRD and switch goalRef when drafted; no empty templates.',
|
|
47
48
|
language: 'en or zh-CN from explicit user preference, otherwise this delivery conversation. Required in current state; retained on update/render/resume.',
|
|
48
49
|
storage: 'Init-only {mode: explicit, decisionRef: delivery-relative-readable-file} for a user-selected location. Otherwise a strict child of configured dataRoot. Returned dataRoot is managed; ordinary workspace/material location is not a choice.',
|
|
49
|
-
stageRuns: 'Ordered
|
|
50
|
-
|
|
50
|
+
stageRuns: 'Ordered execution history, default []; update through record-run, not raw replacement. Every ID is unique; currentRun must match one of these IDs or be null. Stage types may repeat with distinct IDs.',
|
|
51
|
+
batches: 'Stable registered B definitions {id,definitionRef,taskRefs,dependsOn}; register-batches requires the current approved plan. Separate from implementation/repair runs.',
|
|
52
|
+
currentRun: 'Instance ID or null (default); update through set-current. Do not invent an ID without registering its instance in stageRuns.',
|
|
51
53
|
repositories: 'Array of {id, path: absolute-path, role, ...optional-version-facts}, default []. No repository is required for a design-only goal.',
|
|
52
|
-
openChanges: 'Array of {id,
|
|
53
|
-
outcome: '
|
|
54
|
+
openChanges: 'Array of {id,ref,status:open|closed,impactRef?,affectedRefs?}, default []; use record-change; close records rather than deleting history.',
|
|
55
|
+
outcome: 'Managed via record-outcome; defaults to {goal: in-progress, acceptance: not-recorded}. A met goal needs goalConclusionRef; recorded acceptance needs acceptanceRef. Optional handoffRef, mergeRef, deploymentRef retain independent facts.',
|
|
54
56
|
next: '{action, inputRefs?: []}; defaults to continuing the confirmed arrangement.',
|
|
55
57
|
cycle: 'Positive integer, default 1. A reopening increments by one, appends a closureRef and resets current acceptance.',
|
|
56
58
|
closureRefs: 'Historical closure references, default []. Preserve them across reopenings.'
|
package/lib/delivery.mjs
CHANGED
|
@@ -22,42 +22,28 @@ export function arrangeSessions(state) {
|
|
|
22
22
|
for (const r of state.stageRuns) {
|
|
23
23
|
if (r.sessionGroup) continue;
|
|
24
24
|
const category = ['frame','requirements'].includes(r.stage) ? 'requirements' : ['tech-design','contract','prototype'].includes(r.stage) ? 'technical' : r.stage;
|
|
25
|
-
const batch = r.stage === 'review' &&
|
|
25
|
+
const batch = r.stage === 'review' && r.reviewScope==='batch' && r.reviewOf?.length === 1 ? state.stageRuns.find(x=>x.id===r.reviewOf[0]) : null;
|
|
26
26
|
r.sessionGroup = batch ? (batch.sessionGroup ?? `implement:${batch.id}`) : ['requirements','technical','test-design','plan'].includes(category) ? category : `${category}:${r.id}`;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
export function completionStatus(state,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const [i, batch] of implementations.entries()) {
|
|
48
|
-
if (!reviews.some(r=>r.reviewOf?.includes(batch.id) && runs.indexOf(r)>runs.indexOf(batch) && (!implementations[i+1] || runs.indexOf(r)<runs.indexOf(implementations[i+1])))) gaps.push(`Missing passing batch review for ${batch.id}.`);
|
|
49
|
-
}
|
|
50
|
-
const overall=reviews.findLast(r=>r.overall === true && implementations.length && implementations.every(b=>r.reviewOf?.includes(b.id) && runs.indexOf(r)>runs.indexOf(b)));
|
|
51
|
-
if (!overall) gaps.push('Missing passing overall implementation review.');
|
|
52
|
-
const allChecks=runs.filter(r=>r.stage==='verify');
|
|
53
|
-
const checks=allChecks.filter((r,i)=>!allChecks.slice(i+1).some(later=>ready(later) && later.result==='passed' && later.historyResultRefs?.includes(r.resultRef)));
|
|
54
|
-
if (!checks.length || checks.some(r=>!ready(r) || r.result!=='passed' || !overall || runs.indexOf(r)<runs.indexOf(overall))) gaps.push('Independent verification is not fully recorded as passed after overall review.');
|
|
55
|
-
const handoff=runs.findLast(r=>r.stage==='handoff' && ready(r));
|
|
56
|
-
if (handoff && checks.some(r=>runs.indexOf(r)>runs.indexOf(handoff))) gaps.push('Handoff precedes verification.');
|
|
57
|
-
if (state.openChanges.some(c=>c.status==='open')) gaps.push('Unresolved changes remain.');
|
|
58
|
-
const partial=!!state.deliveryPolicy.partialRef;
|
|
59
|
-
const readyForAcceptance=!partial && !gaps.length;
|
|
60
|
-
return {status:partial?'partial-scope':readyForAcceptance && state.outcome.goal==='met' && state.outcome.acceptance==='accepted'?'standard-complete':readyForAcceptance?'ready-for-acceptance':'incomplete',standardComplete:readyForAcceptance && state.outcome.goal==='met' && state.outcome.acceptance==='accepted',readyForAcceptance,gaps:gaps.map(g=>completionMessage(state,g)),partialRef:state.deliveryPolicy.partialRef??null,scope:text(state,'Recorded structure, approvals and results only; not independent proof of test execution or semantic quality.','仅评估已记录的结构、审批和结果;不独立证明测试已执行或内容语义正确。')};
|
|
29
|
+
export function completionStatus(state,review,{changed=[]}={}) {
|
|
30
|
+
const gaps=[],model=review.execution;
|
|
31
|
+
const has=stage=>state.stageRuns.some(r=>r.stage===stage&&review.runs.find(s=>s.id===r.id)?.ready);
|
|
32
|
+
if(state.goal.endpoint===null)gaps.push('Delivery endpoint remains unknown.');
|
|
33
|
+
if(state.documentPolicy.authority===null||unknownConditions(state.documentPolicy).length)gaps.push('Artifact conditions remain unknown.');
|
|
34
|
+
if(changed.length)gaps.push('Changed or missing registered documents require reconciliation.');
|
|
35
|
+
for(const stage of ['requirements','tech-design','test-design','plan','implement','verify','handoff'])if(!has(stage))gaps.push(`Missing current completed ${stage}.`);
|
|
36
|
+
if((state.documentPolicy.interface!==null&&state.documentPolicy.interface!=='none'||state.documentPolicy.databases?.length)&&!has('contract'))gaps.push('Missing applicable interface/SQL contract.');
|
|
37
|
+
if(state.stageRuns.some(r=>r.stage==='prototype')&&!has('prototype'))gaps.push('Missing applicable interaction prototype.');
|
|
38
|
+
if(!model?.batches.length||model.batches.some(b=>b.status!=='complete'))gaps.push('Business batches or their reviews remain unresolved.');
|
|
39
|
+
if(!model?.overall)gaps.push('Missing passing overall implementation review.');
|
|
40
|
+
const handoff=state.stageRuns.findLast(r=>r.stage==='handoff'&&review.runs.find(s=>s.id===r.id)?.ready);
|
|
41
|
+
if(handoff&&model?.verification.runIds.some(id=>state.stageRuns.findIndex(r=>r.id===id)>state.stageRuns.indexOf(handoff)))gaps.push('Handoff precedes verification.');
|
|
42
|
+
if(!model?.verification.passed)gaps.push('Independent verification is not fully recorded as passed after overall review.');
|
|
43
|
+
if(review.runs.some(s=>!s.ready&&!s.resolvedBy&&s.stage!=='verify'))gaps.push('Selected work or document approvals remain unresolved.');
|
|
44
|
+
if(state.openChanges.some(c=>c.status==='open'))gaps.push('Unresolved changes remain.');
|
|
45
|
+
const partial=!!state.deliveryPolicy.partialRef,readyForAcceptance=!partial&&!gaps.length;
|
|
46
|
+
return {status:partial?'partial-scope':readyForAcceptance&&state.outcome.goal==='met'&&state.outcome.acceptance==='accepted'?'standard-complete':readyForAcceptance?'ready-for-acceptance':'incomplete',standardComplete:readyForAcceptance&&state.outcome.goal==='met'&&state.outcome.acceptance==='accepted',readyForAcceptance,gaps:gaps.map(g=>completionMessage(state,g)),partialRef:state.deliveryPolicy.partialRef??null,scope:text(state,'Recorded facts and current dependencies only; not proof of semantic quality.','仅评估记录与当前依赖;不证明业务质量。')};
|
|
61
47
|
}
|
|
62
48
|
// A display suggestion, never a scheduled run or authorization.
|
|
63
49
|
export function suggestedNextStage(state) {
|
|
@@ -67,7 +53,7 @@ export function suggestedNextStage(state) {
|
|
|
67
53
|
if(state.deliveryPolicy.partialRef)return null;
|
|
68
54
|
if(!current)return 'requirements';
|
|
69
55
|
const contracts=(state.documentPolicy.interface!=null && state.documentPolicy.interface!=='none') || state.documentPolicy.databases?.length;
|
|
70
|
-
return ({frame:'requirements',requirements:'tech-design',prototype:'tech-design','tech-design':contracts?'contract':'test-design',contract:'test-design','test-design':'plan',plan:'implement',implement:'review',review:current.overall?'verify':'review',verify:'handoff'})[current.stage]??null;
|
|
56
|
+
return ({frame:'requirements',requirements:'tech-design',prototype:'tech-design','tech-design':contracts?'contract':'test-design',contract:'test-design','test-design':'plan',plan:'implement',implement:'review',review:(current.reviewScope==='overall')?'verify':'review',verify:'handoff'})[current.stage]??null;
|
|
71
57
|
}
|
|
72
58
|
function majorSessionBoundary(state,current) {
|
|
73
59
|
if (!current || current.progress !== 'done') return false;
|
|
@@ -77,7 +63,7 @@ function majorSessionBoundary(state,current) {
|
|
|
77
63
|
if (['tech-design','contract'].includes(current.stage)) return next==='test-design';
|
|
78
64
|
if (current.stage==='plan') return next==='implement';
|
|
79
65
|
if (current.stage==='review' && current.result==='passed') {
|
|
80
|
-
return current.overall ? next==='verify' : !!current.reviewOf?.length && ['implement','review'].includes(next);
|
|
66
|
+
return (current.reviewScope==='overall') ? next==='verify' : !!current.reviewOf?.length && ['implement','review'].includes(next);
|
|
81
67
|
}
|
|
82
68
|
return false;
|
|
83
69
|
}
|
|
@@ -101,7 +87,6 @@ export function sessionAdvice(directory,state,review,{saved=true}={}) {
|
|
|
101
87
|
else if(!current && state.stageRuns.length && review.runs.every(r=>r.ready)) action=concluded?endAction:review.runs.some(r=>r.releaseReady===false)?'resolve-current':endAction;
|
|
102
88
|
else if(current && ((!status?.resolvedBy && (['failed','mixed','unknown','not-run'].includes(current.result) || (current.stage==='review' && current.progress==='done' && !status?.releaseReady))) || current.progress==='blocked' || ['needs-review','unknown'].includes(current.applicability))) action=concluded && !next?'finished':'resolve-current';
|
|
103
89
|
else if(status?.ready) action=!next?endAction:next.progress!=='planned'?'inspect-existing-work':'await-continue';
|
|
104
|
-
if (saved && !pending && state.deliveryPolicy?.version === 1 && status?.ready && next && review.runs.find(r=>r.id===next.id)?.blockedBy.length) action='resolve-current';
|
|
105
90
|
// Internal checkpoints execute nothing and never release another batch.
|
|
106
91
|
let reviewScope=null;
|
|
107
92
|
if (saved && !concluded && !pending && !state.next.questionRef && status?.ready && current?.progress==='done' && !['needs-review','unknown'].includes(current.applicability)) {
|
|
@@ -111,7 +96,7 @@ export function sessionAdvice(directory,state,review,{saved=true}={}) {
|
|
|
111
96
|
const selectedReview=next?.stage==='review' && next.reviewOf?.includes(current.id);
|
|
112
97
|
if(reviewScope && !reviewed && (!state.deliveryPolicy.partialRef || selectedReview))action='continue-implementation-review';
|
|
113
98
|
else if(!reviewScope && !reviewed && implementationType(state,current)==='repair')action='inspect-review-scope';
|
|
114
|
-
} else if(current.stage==='review' && !status.resolvedBy && current.overall!==true && current.reviewOf?.length && ['failed','mixed'].includes(current.result)) action='continue-batch-repair';
|
|
99
|
+
} else if(current.stage==='review' && !status.resolvedBy && (current.reviewScope==='overall')!==true && current.reviewOf?.length && ['failed','mixed'].includes(current.result)) action='continue-batch-repair';
|
|
115
100
|
}
|
|
116
101
|
const suggestNewSession=['await-review','await-continue'].includes(action) && majorSessionBoundary(state,current);
|
|
117
102
|
let instruction=action==='await-clarification'?text(state,'Please answer the question listed below so this work can continue.','请回答下一步所列的具体问题,以便继续当前工作。'):action==='await-continue'?text(state,'This stage is saved. Say “continue” when ready.','本阶段已保存。准备好后可说“继续”。'):action==='assess-outcome'?text(state,'Review the handoff against the agreed goal and record the actual goal conclusion and acceptance; completed handoff alone does not end the delivery.','请对照约定目标审阅交付材料,记录实际目标结论和用户接受结果;完成交付文档不等于交付已结束。'):null;
|
|
@@ -120,5 +105,5 @@ export function sessionAdvice(directory,state,review,{saved=true}={}) {
|
|
|
120
105
|
if(action==='continue-batch-repair')instruction=text(state,'Repair and recheck this batch within the existing authorization; stop for a scope, approval or permission dependency.','在已有授权内修复并复查本批;遇范围、审批或权限依赖时停在实际问题。');
|
|
121
106
|
if(suggestNewSession) instruction=[instruction,action==='await-review'?text(state,'A new session is recommended to review this document; you can also review it here.','建议新开会话审阅本份文档,也可留在这里审核。'):text(state,'A new session is recommended for the next work; you can also continue here.','建议新开会话接续下一步,也可留在这里继续。')].filter(Boolean).join(' ');
|
|
122
107
|
const continuation=['reconcile-save','await-review','await-clarification','assess-outcome','finished'].includes(action)?null:text(state,`Use Aimship to continue delivery ${JSON.stringify(path.resolve(directory))}; first read its current state and relevant formal documents, then continue the recorded work.${instruction?' '+instruction:''}`,`使用 Aimship 继续交付「${path.resolve(directory)}」,先读取当前状态和相关正式文档再接续。${instruction??''}`);
|
|
123
|
-
return {action,stop:!['continue-current','inspect-existing-work','continue-implementation-review','continue-batch-repair','inspect-review-scope'].includes(action),suggestNewSession,reviewScope,currentRun:current?.id??null,nextRun:action==='continue-implementation-review'?(next?.stage==='review' && next.reviewOf?.includes(current.id) && (reviewScope==='overall-recheck' ? next.overall
|
|
108
|
+
return {action,stop:!['continue-current','inspect-existing-work','continue-implementation-review','continue-batch-repair','inspect-review-scope'].includes(action),suggestNewSession,reviewScope,currentRun:current?.id??null,nextRun:action==='continue-implementation-review'?(next?.stage==='review' && next.reviewOf?.includes(current.id) && (reviewScope==='overall-recheck' ? next.reviewScope==='overall' : next.reviewScope==='batch')?next.id:null):['continue-batch-repair','inspect-review-scope'].includes(action)?null:next?.id??null,groups,continuation,instruction,advisory:true,note:text(state,'A session boundary is not a blocker or approval. Continue here when requested. Resolve the delivery identity, current approvals and unfinished effects on every resume; this text grants no approval.','会话边界不是阻塞或批准。用户要求时可在当前会话继续。每次续接都需核对交付身份、当前审批和未完成事项;本提示不授予权限。')};
|
|
124
109
|
}
|
package/lib/documents.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { hash, inside, fail } from './io.mjs';
|
|
4
|
+
import {strippedGenerated} from './projections.mjs';
|
|
4
5
|
|
|
5
6
|
export async function artifactMap() {
|
|
6
7
|
return JSON.parse(await fs.readFile(new URL('../skills/aimship/references/artifact-map.json', import.meta.url), 'utf8'));
|
|
@@ -130,14 +131,14 @@ export async function inspectDocuments(directory, state) {
|
|
|
130
131
|
for (const kind of new Set(docs.map(d=>d.kind))) if (!['sql','source'].includes(kind) && docs.filter(d=>d.kind===kind).length>1) issues.push(`Register one current artifact for ${kind}; keep history through references.`);
|
|
131
132
|
const requirementKind = 'prd';
|
|
132
133
|
const find = kind => docs.find(d => d.kind === (kind === 'requirement' ? requirementKind : kind));
|
|
133
|
-
if (state.schemaVersion ===
|
|
134
|
+
if (state.schemaVersion === 5) {
|
|
134
135
|
try {
|
|
135
136
|
const request = await safeRead(directory,state,'.aimship/init.md');
|
|
136
137
|
if (!request?.trim()) issues.push('Missing or empty .aimship/init.md.');
|
|
137
138
|
else digests['.aimship/init.md'] = hash(request);
|
|
138
139
|
} catch { issues.push('Cannot read .aimship/init.md inside this delivery.'); }
|
|
139
140
|
}
|
|
140
|
-
const preparing = state.schemaVersion ===
|
|
141
|
+
const preparing = state.schemaVersion === 5 && !find('requirement');
|
|
141
142
|
if (preparing && state.goalRef !== '.aimship/init.md') issues.push('Before requirements exist, goalRef must point to .aimship/init.md.');
|
|
142
143
|
if (!preparing && !find('requirement')) issues.push(`Register ${requirementKind} as the requirement entry.`);
|
|
143
144
|
if (!preparing && policy.authority === null) issues.push('Select the requirement authority when registering PRD.');
|
|
@@ -154,7 +155,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
154
155
|
try { text = await safeRead(directory, state, doc.path); }
|
|
155
156
|
catch (error) { issues.push(`Cannot read ${doc.path}: ${error.code}`); continue; }
|
|
156
157
|
if (text === null) { unchecked.push(`${doc.path}: remote or unavailable repository; inspect adopted version using host tools (${doc.basisRef ?? 'missing basisRef'}).`); continue; }
|
|
157
|
-
digests[doc.path] = hash(text);
|
|
158
|
+
digests[doc.path] = hash(doc.mode==='new'?strippedGenerated(text,doc.kind):text);
|
|
158
159
|
if (!text.trim()) issues.push(`${doc.path}: empty document.`);
|
|
159
160
|
if (doc.mode !== 'new') {
|
|
160
161
|
// Reuse is a current input option, not a historical document protocol.
|
|
@@ -178,7 +179,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
178
179
|
const runs = state.stageRuns;
|
|
179
180
|
for (const [i, run] of state.stageRuns.entries()) {
|
|
180
181
|
if (run.progress === 'planned') continue;
|
|
181
|
-
const contract = map.stages[run.stage], doc = find(contract.kind) ?? (run.stage === 'contract' && policy.interface === 'none' ? find('sql') : null);
|
|
182
|
+
const contract = map.stages[run.stage], formerAuthority=run.stage==='requirements'&&run.approvalBasis?.length&&state.goalHistory?.some(h=>h.goalRef?.split('#')[0]===run.resultRef?.split('#')[0])?docs.find(d=>d.kind==='source'&&d.path===run.resultRef?.split('#')[0]):null, doc = formerAuthority ?? find(contract.kind) ?? (run.stage === 'contract' && policy.interface === 'none' ? find('sql') : null);
|
|
182
183
|
if (!doc && preparing && ['frame','requirements'].includes(run.stage) && run.progress !== 'done') continue;
|
|
183
184
|
if (!doc) { issues.push(`${run.id}: missing registered ${contract.kind} artifact.`); continue; }
|
|
184
185
|
if (run.stage === 'implement' && !state.stageRuns.slice(0,i).some(r => r.stage === 'plan' && r.progress === 'done')) issues.push(`${run.id}: complete plan before implementation.`);
|
|
@@ -207,7 +208,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
|
-
if (state.schemaVersion ===
|
|
211
|
+
if (state.schemaVersion === 5) {
|
|
211
212
|
const unknown = unknownConditions(policy);
|
|
212
213
|
const dependent = runs.filter(r=>r.progress !== 'planned' && !['frame','requirements','diagnose'].includes(r.stage));
|
|
213
214
|
if (preparing && dependent.length) issues.push('Prepare and review formal requirements before dependent work.');
|
|
@@ -263,7 +264,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
263
264
|
export async function documentChanges(directory, state) {
|
|
264
265
|
const changes=[];
|
|
265
266
|
for (const [ref, digest] of Object.entries(state.documentDigests ?? {})) {
|
|
266
|
-
try { const text = await safeRead(directory,state,ref); if (text === null || hash(text) !== digest) changes.push(ref); }
|
|
267
|
+
try { const text = await safeRead(directory,state,ref); const doc=state.documents.find(d=>d.path===ref);if (text === null || hash(doc?.mode==='new'?strippedGenerated(text,doc.kind):text) !== digest) changes.push(ref); }
|
|
267
268
|
catch { changes.push(ref); }
|
|
268
269
|
}
|
|
269
270
|
return changes;
|