aimship 0.1.0-preview.13 → 0.1.0-preview.15
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 +12 -10
- package/README.zh-CN.md +12 -10
- package/docs/cli.md +11 -7
- package/docs/guide.md +15 -13
- package/docs/guide.zh-CN.md +15 -13
- package/lib/approvals.mjs +13 -7
- package/lib/cli.mjs +2 -2
- package/lib/delivery.mjs +20 -9
- package/lib/documents.mjs +12 -13
- package/lib/language.mjs +5 -4
- package/lib/state.mjs +3 -3
- package/lib/view.mjs +64 -30
- package/package.json +1 -1
- package/skills/aimship/SKILL.md +4 -3
- package/skills/aimship/references/artifact-map.json +1 -46
- package/skills/aimship/references/core.md +13 -14
- package/skills/aimship/references/document-patterns.md +7 -7
- package/skills/aimship/references/document-review.md +3 -3
- package/skills/aimship/references/hosts.md +1 -1
- package/skills/aimship/references/recovery.md +2 -2
- package/skills/aimship/references/sources-and-changes.md +6 -6
- package/skills/aimship/references/stages/contract.md +1 -1
- package/skills/aimship/references/stages/diagnose.md +1 -1
- package/skills/aimship/references/stages/frame.md +3 -1
- package/skills/aimship/references/stages/handoff.md +3 -1
- package/skills/aimship/references/stages/implement.md +3 -1
- package/skills/aimship/references/stages/plan.md +2 -2
- package/skills/aimship/references/stages/prototype.md +2 -2
- package/skills/aimship/references/stages/requirements.md +3 -3
- package/skills/aimship/references/stages/review.md +3 -1
- package/skills/aimship/references/stages/tech-design.md +2 -2
- package/skills/aimship/references/stages/test-design.md +1 -1
- package/skills/aimship/references/stages/verify.md +2 -0
- package/skills/aimship/references/templates/prd.md +4 -4
- package/skills/aimship/references/stages/scope.md +0 -27
- package/skills/aimship/references/templates/scope.md +0 -34
package/lib/delivery.mjs
CHANGED
|
@@ -18,21 +18,22 @@ export function arrangeSessions(state) {
|
|
|
18
18
|
if (state.deliveryPolicy?.version !== 1) return;
|
|
19
19
|
for (const r of state.stageRuns) {
|
|
20
20
|
if (r.sessionGroup) continue;
|
|
21
|
-
const category = ['frame','requirements'
|
|
21
|
+
const category = ['frame','requirements'].includes(r.stage) ? 'requirements' : ['tech-design','contract','prototype'].includes(r.stage) ? 'technical' : r.stage;
|
|
22
22
|
const batch = r.stage === 'review' && !r.overall && r.reviewOf?.length === 1 ? state.stageRuns.find(x=>x.id===r.reviewOf[0]) : null;
|
|
23
23
|
r.sessionGroup = batch ? (batch.sessionGroup ?? `implement:${batch.id}`) : ['requirements','technical','test-design','plan'].includes(category) ? category : `${category}:${r.id}`;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
export function completionStatus(state, review, {changed=[]}={}) {
|
|
27
27
|
const runs=state.stageRuns, gaps=[];
|
|
28
|
-
if (state.
|
|
28
|
+
if (state.goal.endpoint === null) gaps.push('Delivery endpoint remains unknown.');
|
|
29
|
+
if (state.documentPolicy.authority === null || unknownConditions(state.documentPolicy).length) gaps.push('Artifact conditions remain unknown.');
|
|
29
30
|
if(changed.length) gaps.push('Changed or missing registered documents require reconciliation.');
|
|
30
31
|
const ready=r=>review.runs.find(x=>x.id===r.id)?.ready;
|
|
31
32
|
const has=stage=>runs.some(r=>r.stage===stage && ready(r));
|
|
32
33
|
for (const stage of ['tech-design','test-design','plan','implement','verify','handoff']) if (!has(stage)) gaps.push(`Missing current completed ${stage}.`);
|
|
33
|
-
if (!runs.some(r=>
|
|
34
|
-
if ((state.documentPolicy?.interface!=='none' || state.documentPolicy?.databases?.length) && !has('contract')) gaps.push('Missing applicable interface/SQL contract.');
|
|
35
|
-
if (
|
|
34
|
+
if (!runs.some(r=>r.stage==='requirements' && ready(r))) gaps.push('Missing reviewed requirements.');
|
|
35
|
+
if (((state.documentPolicy?.interface!=null && state.documentPolicy.interface!=='none') || state.documentPolicy?.databases?.length) && !has('contract')) gaps.push('Missing applicable interface/SQL contract.');
|
|
36
|
+
if (runs.some(r=>r.stage==='prototype') && !has('prototype')) gaps.push('Missing applicable interaction prototype.');
|
|
36
37
|
if (review.runs.some(r=>!r.ready)) gaps.push('Selected work or document approvals remain unresolved.');
|
|
37
38
|
const implementations=runs.filter(r=>r.stage==='implement');
|
|
38
39
|
if (implementations.some(r=>['failed','mixed','unknown','not-run'].includes(r.result))) gaps.push('Implementation results remain unresolved.');
|
|
@@ -55,6 +56,16 @@ export function completionStatus(state, review, {changed=[]}={}) {
|
|
|
55
56
|
const readyForAcceptance=!partial && !gaps.length;
|
|
56
57
|
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.','仅评估已记录的结构、审批和结果;不独立证明测试已执行或内容语义正确。')};
|
|
57
58
|
}
|
|
59
|
+
// A display suggestion, never a scheduled run or authorization.
|
|
60
|
+
export function suggestedNextStage(state) {
|
|
61
|
+
const current=state.stageRuns.find(r=>r.id===state.currentRun) ?? state.stageRuns.at(-1);
|
|
62
|
+
const scheduled=current?state.stageRuns[state.stageRuns.indexOf(current)+1]:state.stageRuns[0];
|
|
63
|
+
if(scheduled)return scheduled.stage;
|
|
64
|
+
if(state.deliveryPolicy.partialRef)return null;
|
|
65
|
+
if(!current)return 'requirements';
|
|
66
|
+
const contracts=(state.documentPolicy.interface!=null && state.documentPolicy.interface!=='none') || state.documentPolicy.databases?.length;
|
|
67
|
+
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;
|
|
68
|
+
}
|
|
58
69
|
export function sessionAdvice(directory,state,review,{saved=true}={}) {
|
|
59
70
|
const groups=[];
|
|
60
71
|
for (const r of state.stageRuns) {
|
|
@@ -67,15 +78,15 @@ export function sessionAdvice(directory,state,review,{saved=true}={}) {
|
|
|
67
78
|
const concluded=state.outcome.goal==='met' && review.runs.every(r=>r.ready)
|
|
68
79
|
&& (!!state.deliveryPolicy?.partialRef || completionStatus(state,review).readyForAcceptance);
|
|
69
80
|
const last=state.stageRuns.at(-1);
|
|
70
|
-
const endAction=concluded?'finished':(current??last)?.stage==='handoff'?'assess-outcome':'
|
|
81
|
+
const endAction=concluded?'finished':(current??last)?.stage==='handoff'?'assess-outcome':'await-continue';
|
|
71
82
|
let action='continue-current';
|
|
72
83
|
if (!saved) action='reconcile-save';
|
|
73
84
|
else if(pending) action='await-review';
|
|
74
85
|
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;
|
|
75
86
|
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';
|
|
76
|
-
else if(status?.ready) action=!next?endAction:next.progress!=='planned'?'inspect-existing-work':
|
|
87
|
+
else if(status?.ready) action=!next?endAction:next.progress!=='planned'?'inspect-existing-work':'await-continue';
|
|
77
88
|
if (saved && !pending && state.deliveryPolicy?.version === 1 && status?.ready && next && review.runs.find(r=>r.id===next.id)?.blockedBy.length) action='resolve-current';
|
|
78
|
-
const instruction=action==='
|
|
89
|
+
const instruction=action==='await-continue'?text(state,'This stage is saved. Stop here and wait for the user to continue; a new session is recommended, not required. Approval and continuation may be expressed together.','本阶段已保存。请在此停下,等待用户继续;建议新会话,也可在当前会话继续。批准和继续可以一次表达。'):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;
|
|
79
90
|
const continuation=['reconcile-save','await-review','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??''}`);
|
|
80
|
-
return {action,currentRun:current?.id??null,nextRun: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.','会话边界不是阻塞或批准。用户要求时可在当前会话继续。每次续接都需核对交付身份、当前审批和未完成事项;本提示不授予权限。')};
|
|
91
|
+
return {action,stop:!['continue-current','inspect-existing-work'].includes(action),suggestNewSession:['await-review','await-continue'].includes(action),currentRun:current?.id??null,nextRun: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.','会话边界不是阻塞或批准。用户要求时可在当前会话继续。每次续接都需核对交付身份、当前审批和未完成事项;本提示不授予权限。')};
|
|
81
92
|
}
|
package/lib/documents.mjs
CHANGED
|
@@ -54,7 +54,7 @@ export function sections(text, spec) {
|
|
|
54
54
|
return {id, count:matches.length, start:h?.start, end, body:h?markdownText(text.slice(h.end,end)).trim():null};
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
-
const ID = '(?:REQ|AC|B|TASK|TC|REV|RUN|CHG|Q|PAGE)-[0-9]{3,}';
|
|
57
|
+
const ID = '(?:REQ|FR|BR|PERM|EC|AC|B|TASK|TC|REV|RUN|CHG|Q|PAGE)-[0-9]{3,}';
|
|
58
58
|
export const numberedIds = text => [...new Set(markdownText(text).match(new RegExp('\\b'+ID+'\\b','g'))??[])];
|
|
59
59
|
// A file-qualified Markdown citation names its own readable source. It does not
|
|
60
60
|
// create a second local definition or a namespace/index.
|
|
@@ -71,7 +71,7 @@ function qualifiedCitations(text) {
|
|
|
71
71
|
});
|
|
72
72
|
return {references,local};
|
|
73
73
|
}
|
|
74
|
-
const definitionSections={REQ:['requirements','
|
|
74
|
+
const definitionSections={REQ:['requirements'],FR:['requirements'],BR:['requirements'],PERM:['requirements'],EC:['requirements'],AC:['acceptance'],B:['batches'],TASK:['tasks'],TC:['cases']};
|
|
75
75
|
const referenceHeading=title=>/\b(?:coverage|summary|references|conclusion|notes)\b|覆盖|总结|汇总|引用|结论|备注/i.test(title);
|
|
76
76
|
export function definitions(text, file, spec) {
|
|
77
77
|
const lines=markdownText(text).split('\n'), result=[], context=[];
|
|
@@ -120,15 +120,14 @@ export function hasLocator(text, locator, spec) {
|
|
|
120
120
|
return headings(text).filter(h=>h.title===locator || h.key===normalizeHeading(locator)).length===1;
|
|
121
121
|
}
|
|
122
122
|
export function unknownConditions(policy) {
|
|
123
|
-
return ['interaction','interface','databases'
|
|
123
|
+
return ['interaction','interface','databases'].filter(key=>policy[key] === null);
|
|
124
124
|
}
|
|
125
125
|
export async function inspectDocuments(directory, state) {
|
|
126
126
|
const map = await artifactMap(), issues = [], unchecked = [], digests = {}, defs = [], documentSections = new Map();
|
|
127
127
|
const docs = state.documents ?? [];
|
|
128
128
|
const policy = state.documentPolicy;
|
|
129
129
|
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.`);
|
|
130
|
-
|
|
131
|
-
const requirementKind = policy.authority === 'adopted' ? 'scope' : 'prd';
|
|
130
|
+
const requirementKind = 'prd';
|
|
132
131
|
const find = kind => docs.find(d => d.kind === (kind === 'requirement' ? requirementKind : kind));
|
|
133
132
|
if (state.schemaVersion === 4) {
|
|
134
133
|
try {
|
|
@@ -140,8 +139,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
140
139
|
const preparing = state.schemaVersion === 4 && !find('requirement');
|
|
141
140
|
if (preparing && state.goalRef !== 'source/request.md') issues.push('Before requirements exist, goalRef must point to source/request.md.');
|
|
142
141
|
if (!preparing && !find('requirement')) issues.push(`Register ${requirementKind} as the requirement entry.`);
|
|
143
|
-
if (!preparing && policy.authority === null) issues.push('Select the requirement authority when registering PRD
|
|
144
|
-
if (!preparing && policy.authority !== 'editable' && state.goalRef.split('#')[0] !== `${requirementKind}.md`) issues.push('goalRef must point to the selected root PRD/Scope.');
|
|
142
|
+
if (!preparing && policy.authority === null) issues.push('Select the requirement authority when registering PRD.');
|
|
145
143
|
if (find('requirement') && state.goalRef.split('#')[0]!==find('requirement').path) issues.push('goalRef must point to the registered requirement authority.');
|
|
146
144
|
for (const doc of docs) {
|
|
147
145
|
const spec = map.documents[doc.kind];
|
|
@@ -161,7 +159,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
161
159
|
// Reuse is a current input option, not a historical document protocol.
|
|
162
160
|
// Read ordinary IDs from the explicitly registered requirement authority;
|
|
163
161
|
// do not impose our outline or infer that arbitrary source formats passed.
|
|
164
|
-
if (doc === find('requirement') && /\.md$/i.test(doc.path)) defs.push(...definitions(text,doc.path)
|
|
162
|
+
if (doc === find('requirement') && /\.md$/i.test(doc.path)) defs.push(...definitions(text,doc.path));
|
|
165
163
|
unchecked.push(`${doc.path}: reused format/content; human/Agent basis ${doc.basisRef}.`); continue;
|
|
166
164
|
}
|
|
167
165
|
if (spec) {
|
|
@@ -172,7 +170,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
172
170
|
previous=record.start;
|
|
173
171
|
if(record.count===1 && !record.body) issues.push(`${doc.path}: empty section ${record.id}.`);
|
|
174
172
|
}
|
|
175
|
-
const owned={prd:['REQ','
|
|
173
|
+
const owned={prd:['REQ','FR','BR','PERM','EC','AC','PAGE'],plan:['B','TASK'],testing:['TC','RUN'],review:['REV'],'change-log':['CHG']}[doc.kind]??[];
|
|
176
174
|
defs.push(...definitions(text, doc.path, spec).filter(d=>owned.includes(d.id.split('-')[0]) || d.id.startsWith('Q-')));
|
|
177
175
|
}
|
|
178
176
|
}
|
|
@@ -180,7 +178,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
180
178
|
for (const [i, run] of state.stageRuns.entries()) {
|
|
181
179
|
if (run.progress === 'planned') continue;
|
|
182
180
|
const contract = map.stages[run.stage], doc = find(contract.kind) ?? (run.stage === 'contract' && policy.interface === 'none' ? find('sql') : null);
|
|
183
|
-
if (!doc && preparing && ['frame','requirements'
|
|
181
|
+
if (!doc && preparing && ['frame','requirements'].includes(run.stage) && run.progress !== 'done') continue;
|
|
184
182
|
if (!doc) { issues.push(`${run.id}: missing registered ${contract.kind} artifact.`); continue; }
|
|
185
183
|
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.`);
|
|
186
184
|
if (run.progress === 'done') {
|
|
@@ -210,7 +208,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
210
208
|
}
|
|
211
209
|
if (state.schemaVersion === 4) {
|
|
212
210
|
const unknown = unknownConditions(policy);
|
|
213
|
-
const dependent = runs.filter(r=>r.progress !== 'planned' && !['frame','requirements','
|
|
211
|
+
const dependent = runs.filter(r=>r.progress !== 'planned' && !['frame','requirements','diagnose'].includes(r.stage));
|
|
214
212
|
if (preparing && dependent.length) issues.push('Prepare and review formal requirements before dependent work.');
|
|
215
213
|
if (state.goal.endpoint === null && dependent.length) issues.push('Resolve the delivery endpoint before dependent work; initialization grants no further authorization.');
|
|
216
214
|
if (unknown.length && runs.some(r=>(r.stage === 'tech-design' && r.progress === 'done') || (['contract','test-design','plan','implement'].includes(r.stage) && r.progress !== 'planned'))) issues.push(`Resolve artifact conditions before completing design or dependent work: ${unknown.join(', ')}.`);
|
|
@@ -220,7 +218,7 @@ export async function inspectDocuments(directory, state) {
|
|
|
220
218
|
const doc=find('contract');
|
|
221
219
|
if (doc?.mode === 'new' && doc.path !== 'openapi.yaml') issues.push('New HTTP contract must be openapi.yaml.');
|
|
222
220
|
}
|
|
223
|
-
if (
|
|
221
|
+
if (runs.some(r=>r.stage==='implement' && r.progress!=='planned') && runs.some(r=>r.stage==='prototype' && r.progress!=='done')) issues.push('Complete the selected prototype before implementation.');
|
|
224
222
|
const seen=new Set();
|
|
225
223
|
for(const d of defs) {
|
|
226
224
|
if(seen.has(d.id)) issues.push(`Duplicate definition ${d.id}.`);
|
|
@@ -246,8 +244,9 @@ export async function inspectDocuments(directory, state) {
|
|
|
246
244
|
|| (state.reviewHistory??[]).some(r=>r.document===doc.path && r.version===doc.version && (r.kind==='approved' || (r.kind==='self-check' && r.result==='passed')));
|
|
247
245
|
const records=documentSections.get(doc.path)??[];
|
|
248
246
|
if(ready) {
|
|
247
|
+
if(doc.kind==='prd' && !defs.some(d=>d.file===doc.path && /^(REQ|FR|BR|PERM|EC)-/.test(d.id))) issues.push(`${doc.path}: include substantive requirement records (REQ or adopted business IDs).`);
|
|
249
248
|
for(const section of records) if(!meaningful(section.body??'')) issues.push(`${doc.path}: replace template guidance with substantive ${section.id} content.`);
|
|
250
|
-
for(const prefix of ({prd:['
|
|
249
|
+
for(const prefix of ({prd:['AC'],testing:['TC'],plan:['TASK']}[doc.kind]??[])) {
|
|
251
250
|
if(!defs.some(d=>d.file===doc.path && d.id.startsWith(prefix+'-'))) issues.push(`${doc.path}: include substantive ${prefix} records.`);
|
|
252
251
|
}
|
|
253
252
|
}
|
package/lib/language.mjs
CHANGED
|
@@ -3,12 +3,12 @@ export const languageOf = state => state.language;
|
|
|
3
3
|
export const text = (state, english, chinese) => languageOf(state) === 'zh-CN' ? chinese : english;
|
|
4
4
|
const labels = {
|
|
5
5
|
'not recorded':'未记录', unknown:'未知', null:'未知', true:'是', false:'否', none:'不适用', http:'HTTP', other:'其他',
|
|
6
|
-
technical:'技术与契约', unrecorded:'未分组', unavailable:'不可用', 'assess-outcome':'核对交付结论与接受结果', frame:'目标梳理', requirements:'需求',
|
|
6
|
+
technical:'技术与契约', unrecorded:'未分组', unavailable:'不可用', 'assess-outcome':'核对交付结论与接受结果', frame:'目标梳理', requirements:'需求', prototype:'原型', 'tech-design':'技术设计', contract:'接口与 SQL', 'test-design':'测试设计', plan:'实施计划', implement:'实施', review:'审查', verify:'验证', handoff:'交付验收', diagnose:'调查',
|
|
7
7
|
planned:'待开始', active:'进行中', blocked:'受阻', done:'已完成记录', passed:'通过', failed:'失败', 'not-run':'未执行', mixed:'部分通过', current:'当前有效', 'needs-review':'待复核',
|
|
8
8
|
'in-progress':'进行中', met:'已达成', 'not-met':'未达成', 'not-recorded':'未记录', accepted:'已接受', declined:'未接受',
|
|
9
9
|
approved:'已批准', pending:'待审核', 'self-check-required':'待自检', 'self-check-failed':'自检失败', 'changes-requested':'需修改', 'evidence-changed':'审批依据已变化',
|
|
10
10
|
'partial-scope':'明确限定范围', 'standard-complete':'标准交付已完成', 'ready-for-acceptance':'待验收', incomplete:'未完成',
|
|
11
|
-
'continue-current':'继续当前工作', 'continue
|
|
11
|
+
'continue-current':'继续当前工作', 'await-continue':'等待继续', 'inspect-existing-work':'检查已有工作', 'resolve-current':'先处理当前问题', 'await-review':'等待审核', 'reconcile-save':'核对保存结果', finished:'当前约定目标已结束', 'arrange-next':'确认并记录下一项工作',
|
|
12
12
|
'requirements preparation':'需求准备', supported:'已启用', enforced:'已启用',
|
|
13
13
|
interaction:'交互变化', prototypeCovered:'已有原型覆盖', interface:'接口变化', databases:'数据库变化'
|
|
14
14
|
};
|
|
@@ -17,9 +17,10 @@ export function displayValue(state,value) {
|
|
|
17
17
|
return languageOf(state)==='zh-CN' ? (labels[raw] ?? raw) : raw;
|
|
18
18
|
}
|
|
19
19
|
const explanations = {
|
|
20
|
-
'
|
|
20
|
+
'Delivery endpoint remains unknown.':'交付终点尚待确认。',
|
|
21
|
+
'Artifact conditions remain unknown.':'产物适用条件待技术设计确认。',
|
|
21
22
|
'Changed or missing registered documents require reconciliation.':'已登记文档有变化或缺失,需要核对。',
|
|
22
|
-
'Missing reviewed requirements
|
|
23
|
+
'Missing reviewed requirements.':'需求文档尚未完成审核。',
|
|
23
24
|
'Missing applicable interface/SQL contract.':'缺少本次适用的接口或 SQL 契约。',
|
|
24
25
|
'Missing applicable interaction prototype.':'缺少本次适用的交互原型。',
|
|
25
26
|
'Selected work or document approvals remain unresolved.':'已安排工作或文档审批仍有未完成项。',
|
package/lib/state.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
10
10
|
import { atomicWrite, exists, fail, hash, inside, json, noSymlinks, readJSON, withLock } from './io.mjs';
|
|
11
11
|
|
|
12
12
|
export const SCHEMA_VERSION = 4;
|
|
13
|
-
export const STAGES = ['frame', 'requirements', 'prototype', '
|
|
13
|
+
export const STAGES = ['frame', 'requirements', 'prototype', 'diagnose', 'tech-design', 'contract', 'test-design', 'plan', 'implement', 'review', 'verify', 'handoff'];
|
|
14
14
|
const PROGRESS = ['planned', 'active', 'blocked', 'done'];
|
|
15
15
|
const RESULTS = ['passed', 'failed', 'not-run', 'unknown', 'mixed'];
|
|
16
16
|
const OUTCOME_REFS = ['goalConclusionRef', 'acceptanceRef', 'mergeRef', 'deploymentRef', 'handoffRef'];
|
|
@@ -42,7 +42,7 @@ export function validateState(state) {
|
|
|
42
42
|
requireThat(Array.isArray(state.documents), 'documents must be an array.');
|
|
43
43
|
const paths = new Set();
|
|
44
44
|
for (const d of state.documents) {
|
|
45
|
-
requireThat(object(d) && ['prd','
|
|
45
|
+
requireThat(object(d) && ['prd','tech-design','plan','testing','review','diagnosis','handoff','change-log','prototype','contract','sql','source'].includes(d.kind) && ['new','reused'].includes(d.mode) && string(d.version), 'Invalid document registration.');
|
|
46
46
|
ref(d.path, 'documents.path'); requireThat(!d.path.includes('#') && !paths.has(d.path), 'Document paths must be unique and have no fragment.'); paths.add(d.path);
|
|
47
47
|
if (d.assetRefs !== undefined) refs(d.assetRefs, 'documents.assetRefs');
|
|
48
48
|
if (d.mode === 'reused') { ref(d.basisRef, 'documents.basisRef'); }
|
|
@@ -191,7 +191,7 @@ export async function updateState(directory, patch, revision, rulesVersion, runt
|
|
|
191
191
|
requireThat(previous.deliveryPolicy === undefined || next.deliveryPolicy?.version === previous.deliveryPolicy.version, 'Cannot remove or downgrade deliveryPolicy.');
|
|
192
192
|
arrangeSessions(next); validateState(next);
|
|
193
193
|
// Registering the first formal basis is not a new user scope decision.
|
|
194
|
-
const formalizing = previous.schemaVersion === 4 && previous.goalRef === 'source/request.md' && !previous.documents.some(d=>
|
|
194
|
+
const formalizing = previous.schemaVersion === 4 && previous.goalRef === 'source/request.md' && !previous.documents.some(d=>d.kind==='prd') && next.documents.some(d=>d.kind==='prd' && d.path === next.goalRef.split('#')[0]);
|
|
195
195
|
if (JSON.stringify(previous.goal) !== JSON.stringify(next.goal) || (previous.goalRef !== next.goalRef && !formalizing)) {
|
|
196
196
|
requireThat(string(next.goal.decisionRef) && next.goal.decisionRef !== previous.goal.decisionRef, 'Goal revision requires a new explicit decisionRef; viewing results does not revise a goal.');
|
|
197
197
|
next.goalHistory = [...(previous.goalHistory ?? []), { goal: previous.goal, goalRef: previous.goalRef, deliveryPolicy: previous.deliveryPolicy, outcome: previous.outcome, revision: previous.revision }];
|
package/lib/view.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { text, displayValue } from './language.mjs';
|
|
2
|
-
import { completionStatus, sessionAdvice } from './delivery.mjs';
|
|
2
|
+
import { completionStatus, sessionAdvice, suggestedNextStage } from './delivery.mjs';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import { reviewStatus } from './approvals.mjs';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -28,37 +28,71 @@ async function available(directory,state,ref) {
|
|
|
28
28
|
const p=resolveRef(directory,state,ref); return p && await exists(p);
|
|
29
29
|
}
|
|
30
30
|
export async function renderText(directory,state) {
|
|
31
|
-
const t=(en,zh)=>text(state,en,zh), v=x=>cell(displayValue(state,x))
|
|
32
|
-
const artifactLink=ref=>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const t=(en,zh)=>text(state,en,zh), v=x=>cell(displayValue(state,x));
|
|
32
|
+
const artifactLink=ref=>ref.startsWith('repo:')
|
|
33
|
+
? `[${cell(ref.split('#')[0])}](${encodeURI(resolveRef(directory,state,ref)).replaceAll('(','%28').replaceAll(')','%29')})`
|
|
34
|
+
: link(ref.split('#')[0]);
|
|
35
|
+
const review=await reviewStatus(directory,state), changed=await documentChanges(directory,state);
|
|
36
|
+
const completion=completionStatus(state,review,{changed}), session=sessionAdvice(directory,state,review);
|
|
37
|
+
const current=state.stageRuns.find(r=>r.id===state.currentRun) ?? state.stageRuns.at(-1);
|
|
38
|
+
const currentReview=review.runs.find(r=>r.id===current?.id);
|
|
39
|
+
const humanDocs=review.documents.filter(d=>currentReview?.documents.includes(d.path));
|
|
40
|
+
const failed=r=>['failed','mixed','unknown','not-run'].includes(r.result);
|
|
41
|
+
const progress=r=> {
|
|
42
|
+
const check=review.runs.find(x=>x.id===r.id);
|
|
43
|
+
if(r.progress==='planned')return t('Not started','尚未开始');
|
|
44
|
+
if(r.progress==='blocked' || check?.blockedBy.length || ['needs-review','unknown'].includes(r.applicability))return t('Needs attention','需要处理');
|
|
45
|
+
if(failed(r) && !check?.resolvedBy)return t('Result needs resolution','结果待处理');
|
|
46
|
+
if(check?.documents.some(file=>['self-check-failed','changes-requested','evidence-changed','unavailable'].includes(review.documents.find(d=>d.path===file)?.status)))return t('Needs revision or checking','需要修改或核对');
|
|
47
|
+
if(r.progress==='done' && check?.waiting.length)return t('Document prepared; awaiting review','文档已完成,待审核');
|
|
48
|
+
return r.progress==='done'?t('Completed','已完成'):t('In progress','进行中');
|
|
36
49
|
};
|
|
37
|
-
const lines=[t('# Delivery
|
|
38
|
-
lines.push(`${t('
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
lines.push(
|
|
42
|
-
for(const r of state.repositories)lines.push(`| ${cell(r.id)} | ${cell(r.path)} | ${cell(r.role)} |`);
|
|
43
|
-
if(!state.repositories.length)lines.push(t('| — | No repository registered | — |','| — | 尚未关联代码仓库 | — |'));
|
|
44
|
-
lines.push('',t('## Stage progress','## 阶段进度'),'',t('| Run / scope | Stage | Progress | Result | Applicability |','| 工作记录 / 范围 | 阶段 | 进度 | 结果 | 有效性 |'),'| --- | --- | --- | --- | --- |');
|
|
45
|
-
for(const r of state.stageRuns)lines.push(`| ${cell(r.id)} / ${cell(r.scope)} | ${v(r.stage)} | ${v(r.progress)} | ${v(r.result)} | ${v(r.applicability)} |`);
|
|
46
|
-
lines.push('',t('## Current work, blockers and next action','## 当前工作、阻塞与下一步'),'',`${t('Current','当前工作')}: ${identifier(state.currentRun)}`,'',`${t('Next','下一步')}: ${cell(state.next.action)}`);
|
|
47
|
-
for(const r of state.stageRuns.filter(r=>r.progress==='blocked'))lines.push(`- ${t('Blocked','受阻')}: ${cell(r.id)} — ${cell(r.scope)}`);
|
|
48
|
-
for(const c of state.openChanges.filter(c=>c.status==='open'))lines.push(`- ${t('Open change/question','待处理变更或问题')}: ${link(c.ref)}`);
|
|
49
|
-
const review=await reviewStatus(directory,state);
|
|
50
|
-
lines.push('',t('## Document review','## 文档审核'),'',`${t('Review protocol','审核协议')}: ${v(review.compatibility)}`,`${t('Next stage','下一阶段')}: ${v(review.nextStage)}`,'',t('| Document | Version | Review status | Approval record |','| 文档 | 修订 | 审核状态 | 批准记录 |'),'| --- | --- | --- | --- |');
|
|
51
|
-
for(const d of review.documents)lines.push(`| ${artifactLink(d.path)} | ${cell(d.version)} | ${v(d.status)} | ${identifier(d.approval)} |`);
|
|
52
|
-
for(const p of review.pending)lines.push(`- ${t('Awaiting review','待审核')}: ${artifactLink(p.document)} (${cell(p.run)}; ${v(p.status)})`);
|
|
53
|
-
lines.push('',t('Approval records bind content and user decision evidence; the tool does not authenticate the user.','批准记录关联文档内容与用户决定依据;工具不认证用户身份。'),'');
|
|
54
|
-
const completion=completionStatus(state,review,{changed:await documentChanges(directory,state)}),session=sessionAdvice(directory,state,review);
|
|
55
|
-
lines.push('',t('## Delivery completeness','## 交付完整性'),'',`${t('Assessment','评估')}: ${v(completion.status)}`,`${t('Standard complete','标准交付完成')}: ${v(completion.standardComplete)}`,...completion.gaps.map(g=>`- ${cell(g)}`),'',t('Historical goal/acceptance values are separate from this assessment.','历史目标与接受记录独立保留,不替代本次完整性评估。'),'',t('## Session suggestions','## 会话建议'),'',t('Requirements → technical/interface/SQL → test cases → plan → each implementation batch with its review → overall review → each verification batch. Only applicable work is grouped; these are context suggestions, not gates.','需求 → 技术设计与适用的接口/SQL → 测试用例 → 实施计划 → 各实施批次及其审查 → 整体审查 → 各验证批次。仅分组适用工作;这是上下文建议,不是额外审批门槛。'),'',...session.groups.map(g=>`- ${cell(g.id)}: ${g.runs.map(cell).join(', ')}`),'',`${t('Next interaction','下一项操作')}: ${v(session.action)}`);
|
|
50
|
+
const lines=[t('# Delivery progress','# 交付进度'),'',cell(state.goal.summary),'',t('## Current stage and your next action','## 当前阶段与用户操作'),''];
|
|
51
|
+
lines.push(`${t('Current stage','当前阶段')}: ${v(current?.stage??'requirements preparation')}${current?' · '+progress(current):''}`);
|
|
52
|
+
for(const d of humanDocs)lines.push(`- ${artifactLink(d.path)} ${cell(d.version)} · ${d.selfCheck?t('Self-check recorded as passed','自检记录通过'):t('Self-check pending','待自检')} · ${v(d.status)}`);
|
|
53
|
+
lines.push('',`${t('Next action','下一步')}: ${cell(state.next.action)}`);
|
|
54
|
+
if(session.action==='await-review')lines.push('',t('Review the current document and explicitly approve it or give feedback. Do not start the next stage while review is pending.','请审核当前材料,明确通过或反馈修改;待审期间不进入下一阶段。'));
|
|
56
55
|
if(session.instruction)lines.push('',cell(session.instruction));
|
|
57
|
-
|
|
58
|
-
lines.push(''
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
const next=suggestedNextStage(state);
|
|
57
|
+
if(next && current?.progress==='done' && !['resolve-current','finished','assess-outcome'].includes(session.action))lines.push('',`${t('After this stop','当前停点处理后')}: ${v(next)}${session.action==='await-review'?t(' (after approval)','(批准后)'):''}`);
|
|
58
|
+
const problems=[];
|
|
59
|
+
if(current && current.progress!=='planned') {
|
|
60
|
+
if(current.progress==='blocked')problems.push(cell(current.scope));
|
|
61
|
+
if(currentReview?.blockedBy.length)problems.push(t('Current prerequisites need completion or approval.','当前前置工作需要完成或批准。'));
|
|
62
|
+
if(failed(current) && !currentReview?.resolvedBy)problems.push(t('Resolve the recorded unsuccessful result before advancing.','请先处理当前未通过的结果,再推进。'));
|
|
63
|
+
if(['needs-review','unknown'].includes(current.applicability))problems.push(t('Reassess the affected current work.','请复核当前受影响的工作。'));
|
|
64
|
+
}
|
|
65
|
+
for(const ref of changed)problems.push(`${t('Changed or missing material','材料已变化或缺失')}: ${artifactLink(ref)}`);
|
|
66
|
+
for(const c of state.openChanges.filter(c=>c.status==='open'))problems.push(`${t('Open change','待处理变更')}: ${artifactLink(c.ref)}`);
|
|
67
|
+
if(review.evidenceIssues.length)problems.push(t('Recorded review evidence needs checking.','已记录的审核依据需要核对。'));
|
|
68
|
+
if(problems.length)lines.push('',t('## Items to resolve','## 当前待处理事项'),'',...problems.map(p=>'- '+p));
|
|
69
|
+
lines.push('',t('## Delivery progress','## 整体进度'),'',t('| Stage / work | Status |','| 阶段/工作 | 状态 |'),'| --- | --- |');
|
|
70
|
+
for(const r of state.stageRuns)lines.push(`| ${v(r.stage)} · ${cell(r.scope)} | ${progress(r)} |`);
|
|
71
|
+
if(!state.deliveryPolicy.partialRef)for(const stage of ['requirements','tech-design','contract','test-design','plan','implement','review','verify','handoff']) {
|
|
72
|
+
if(state.stageRuns.some(r=>r.stage===stage))continue;
|
|
73
|
+
if(stage==='contract' && state.documentPolicy.interface==='none' && state.documentPolicy.databases?.length===0)continue;
|
|
74
|
+
const unknown=stage==='contract' && (state.documentPolicy.interface===null || state.documentPolicy.databases===null);
|
|
75
|
+
lines.push(`| ${v(stage)} | ${unknown?t('Applicability to be determined during design','待技术设计确认是否适用'):t('Not started','尚未开始')} |`);
|
|
76
|
+
}
|
|
77
|
+
lines.push('',`${t('Delivery conclusion','交付结论')}: ${v(completion.status)}`);
|
|
78
|
+
// Full completion diagnostics matter at handoff, not as a list of early-stage defects.
|
|
79
|
+
if(current?.stage==='handoff' || state.outcome.goal==='met')lines.push(...completion.gaps.map(g=>'- '+cell(g)));
|
|
80
|
+
if(state.outcome.acceptance!=='not-recorded')lines.push(`${t('User acceptance','用户验收')}: ${v(state.outcome.acceptance)}`);
|
|
81
|
+
if(state.outcome.mergeRef)lines.push(`${t('Merge record','合并记录')}: ${artifactLink(state.outcome.mergeRef)}`);
|
|
82
|
+
if(state.outcome.deploymentRef)lines.push(`${t('Deployment record','部署记录')}: ${artifactLink(state.outcome.deploymentRef)}`);
|
|
83
|
+
lines.push('',t('## Goal and adopted materials','## 目标与采用材料'),'',`${t('Endpoint','交付终点')}: ${cell(state.goal.endpoint??t('To be confirmed','待确认'))}`);
|
|
84
|
+
if(state.documentPolicy.interface===null || state.documentPolicy.databases===null)lines.push('',t('Interface/database applicability will be determined during technical design.','接口/数据库适用性待技术设计确认。'));
|
|
85
|
+
const refs=new Map();
|
|
86
|
+
const names={testing:t('Test cases and results','用例与结果'),sql:'SQL',prd:t('Requirements','需求依据'),source:t('Source material','来源材料'),prototype:t('Prototype','原型')};
|
|
87
|
+
for(const d of state.documents)refs.set(d.path,`${names[d.kind]??v(d.kind)} · ${cell(d.version)}`);
|
|
88
|
+
for(const ref of state.stageRuns.flatMap(r=>[...r.outputRefs,r.resultRef].filter(Boolean)).concat(state.next.inputRefs??[]))if(!refs.has(ref.split('#')[0]))refs.set(ref.split('#')[0],t('Work material','工作材料'));
|
|
89
|
+
for(const [ref,role] of refs)if(await available(directory,state,ref))lines.push(`- ${role}: ${artifactLink(ref)}`);
|
|
90
|
+
lines.push('',t('## Continue this delivery','## 接续本次交付'),'',`${t('Delivery directory','交付目录')}: ${cell(path.resolve(directory))}`,'',t('Explicitly activate Aimship in a new session, name this directory and read the saved state and current materials. Resume this stop; “continue” is not approval. A new session is recommended at stage boundaries, but you may continue here.','新会话明确启用 Aimship,指定此目录,读取已保存状态和当前材料,接续本页停点;“继续”不代表批准。阶段边界建议新会话,也可在当前会话继续。'));
|
|
91
|
+
if(state.repositories.length) {
|
|
92
|
+
lines.push('',t('## Associated engineering','## 关联工程'),'',t('| Object | Location | Role |','| 工程 | 位置 | 用途 |'),'| --- | --- | --- |');
|
|
93
|
+
for(const r of state.repositories)lines.push(`| ${cell(r.id)} | ${cell(r.path)} | ${cell(r.role)} |`);
|
|
94
|
+
}
|
|
95
|
+
lines.push('',t('## Saved information','## 保存信息'),'',`${t('Delivery','交付')}: ${state.deliveryId}`,`${t('State revision','状态修订')}: ${state.revision}`,`${t('Rules','规则版本')}: ${state.rulesVersion}`,`${t('State saved','状态保存时间')}: ${state.updatedAt}`,'',t('Generated from saved state and document checks. Update the formal records, not this page. Recorded results are not independent proof of execution.','由保存状态和文档检查生成;请更新正式记录,不手改本页。记录结果不等于独立验证了实际执行。'),'');
|
|
62
96
|
return lines.join('\n');
|
|
63
97
|
}
|
|
64
98
|
export async function generateView(directory,state,preserveEdits=false) {
|
package/package.json
CHANGED
package/skills/aimship/SKILL.md
CHANGED
|
@@ -9,10 +9,11 @@ disable-model-invocation: true
|
|
|
9
9
|
Activate only when the user explicitly asks to start or resume Aimship. A clear request such as “use Aimship to start/continue” is explicit activation; a passing mention of its name or opening a project is not. In each new session, explicitly activate it again. Ordinary conversation does not create a delivery. Once activated, continue the same delivery across turns without asking the user to invoke every stage.
|
|
10
10
|
|
|
11
11
|
1. Run `"{{AIMSHIP_NODE}}" "{{AIMSHIP_LAUNCHER}}" bind` to resolve the installed version. Read the returned fixed-version core and that CLI’s help before using its state protocol; cached old-tool schema metadata is not the newly bound protocol. Use that fixed version's tools, carrying its rules version and the current state revision. If this is a source checkout rather than an installed entry, use `node bin/aimship.mjs bind` from the package root.
|
|
12
|
-
2. Read the core at the path returned by `bind`. Resolve all supporting links below against that same fixed release, even if a cached entry mentions an older path. [The shared core](references/core.md) is the source-checkout reference. For an existing delivery, read its state and the current goal/
|
|
12
|
+
2. Read the core at the path returned by `bind`. Resolve all supporting links below against that same fixed release, even if a cached entry mentions an older path. [The shared core](references/core.md) is the source-checkout reference. For an existing delivery, read its state and the current goal/PRD plus only the next step's required references. Do not create a replacement delivery, reset its plan, or infer a new goal from “continue”.
|
|
13
13
|
3. Follow the core's [initialization flow](references/core.md#initialization) for new work or interrupted creation. A concrete problem or intended change is enough; no PRD, code location, repository association or known technical conditions are required. Preserve the original request, resolve the same delivery and access, and use init only when state does not exist. New deliveries default to a separate folder under the bound dataRoot; the AI tool workspace or prototype location is not an explicit location choice. If access is needed, have the user add the actual delivery directory or grant access in the AI tool, without copying documents or claiming automatic folder addition. Save the delivery language from the user’s preference or current conversation, as the core specifies. Unknown endpoint/conditions remain unknown, never an inferred authorization or limited scope. Report actual save location, current work and next step. Repeated activation resumes; later code association retains the same delivery ID.
|
|
14
|
-
4. Read the selected [stage reference](references/core.md#stage-references)
|
|
15
|
-
5.
|
|
14
|
+
4. Read the selected [stage reference](references/core.md#stage-references). Work continuously within that stage. On completion and self-check, save its artifacts, current stop and next action, report them and stop. Follow [document review](references/document-review.md): reviewable documents await explicit approval; other stage results await continuation, not a new approval. A user may approve and ask to continue in one message. “Continue”, questions and silence alone do not approve. Do not automatically cross a stage because its sessionGroup is unchanged.
|
|
15
|
+
5. Recommend a new session at a saved stage boundary, without requiring it. On resume, uniquely locate the actual delivery and read its saved review/unfinished-work stop; never infer approval from a copied entry. Keep a compact binding: delivery ID, absolute directory, state path, current run, rules version and fixed core/tool paths. Reload these after compaction if absent. Default to the standard delivery sequence, not a full/design/init choice; respect explicit user limits or pauses.
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
Read [recovery and updates](references/recovery.md) on interruption, version mismatch or missing references; [sources and change flow](references/sources-and-changes.md) when adopting external material or revising reviewed work; [AI tool access](references/hosts.md) only when access or tool setup needs attention. Read [document patterns](references/document-patterns.md) when establishing the artifact arrangement or producing a document. New deliveries keep formal documents at the delivery directory root, source/request.md under source/, and structured state under .aimship/; status.md is the generated reading entry. On resume use the actual statePath returned by read, inspect view/document-change warnings, and explicitly repair supported state as described in recovery. Do not treat a generated view as editable authority.
|
|
18
19
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"en": [
|
|
17
17
|
"Document information and sources",
|
|
18
18
|
"Background, goals and non-goals",
|
|
19
|
-
"Scope
|
|
19
|
+
"Scope and delivery endpoint",
|
|
20
20
|
"Requirements and behavior",
|
|
21
21
|
"States, interaction and recovery",
|
|
22
22
|
"Acceptance criteria",
|
|
@@ -33,38 +33,6 @@
|
|
|
33
33
|
]
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
"scope": {
|
|
37
|
-
"path": "scope.md",
|
|
38
|
-
"sections": [
|
|
39
|
-
"info",
|
|
40
|
-
"goals",
|
|
41
|
-
"adoption",
|
|
42
|
-
"exclusions",
|
|
43
|
-
"decisions",
|
|
44
|
-
"acceptance",
|
|
45
|
-
"arrangement"
|
|
46
|
-
],
|
|
47
|
-
"headings": {
|
|
48
|
-
"en": [
|
|
49
|
-
"Document information and source versions",
|
|
50
|
-
"Goal, engineering objects and endpoint",
|
|
51
|
-
"Source mapping and adopted scope",
|
|
52
|
-
"Exclusions and constraints",
|
|
53
|
-
"Conflicts, gaps and supplementary decisions",
|
|
54
|
-
"Acceptance basis",
|
|
55
|
-
"Authorization, questions and stage arrangement"
|
|
56
|
-
],
|
|
57
|
-
"zh-CN": [
|
|
58
|
-
"文档信息与来源",
|
|
59
|
-
"目标与交付终点",
|
|
60
|
-
"采用范围与来源",
|
|
61
|
-
"排除范围",
|
|
62
|
-
"决定与冲突",
|
|
63
|
-
"验收标准",
|
|
64
|
-
"阶段安排"
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
36
|
"tech-design": {
|
|
69
37
|
"path": "tech-design.md",
|
|
70
38
|
"sections": [
|
|
@@ -262,19 +230,6 @@
|
|
|
262
230
|
"requirements": {
|
|
263
231
|
"kind": "requirement",
|
|
264
232
|
"section": "requirements",
|
|
265
|
-
"sectionByKind": {
|
|
266
|
-
"prd": "requirements",
|
|
267
|
-
"scope": "adoption"
|
|
268
|
-
},
|
|
269
|
-
"humanReview": true
|
|
270
|
-
},
|
|
271
|
-
"scope": {
|
|
272
|
-
"kind": "requirement",
|
|
273
|
-
"section": "scope",
|
|
274
|
-
"sectionByKind": {
|
|
275
|
-
"prd": "scope",
|
|
276
|
-
"scope": "goals"
|
|
277
|
-
},
|
|
278
233
|
"humanReview": true
|
|
279
234
|
},
|
|
280
235
|
"prototype": {
|