aimship 0.1.0-preview.11 → 0.1.0-preview.13
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 -10
- package/README.zh-CN.md +6 -10
- package/docs/cli.md +23 -25
- package/docs/guide.md +314 -148
- package/docs/guide.zh-CN.md +314 -148
- package/lib/approvals.mjs +77 -13
- package/lib/cli.mjs +27 -21
- package/lib/delivery.mjs +15 -12
- package/lib/discovery.mjs +25 -0
- package/lib/documents.mjs +159 -65
- package/lib/install.mjs +1 -1
- package/lib/language.mjs +41 -0
- package/lib/location.mjs +65 -0
- package/lib/state.mjs +54 -87
- package/lib/view.mjs +36 -34
- package/package.json +1 -1
- package/skills/aimship/SKILL.md +3 -3
- package/skills/aimship/references/artifact-map.json +165 -9
- package/skills/aimship/references/core.md +17 -13
- package/skills/aimship/references/document-patterns.md +17 -11
- package/skills/aimship/references/document-review.md +1 -3
- package/skills/aimship/references/hosts.md +11 -9
- package/skills/aimship/references/recovery.md +9 -13
- package/skills/aimship/references/sources-and-changes.md +11 -4
- package/skills/aimship/references/stages/diagnose.md +1 -1
- package/skills/aimship/references/stages/frame.md +1 -1
- package/skills/aimship/references/stages/handoff.md +1 -1
- package/skills/aimship/references/stages/implement.md +2 -2
- package/skills/aimship/references/stages/plan.md +1 -1
- package/skills/aimship/references/stages/requirements.md +1 -1
- package/skills/aimship/references/stages/review.md +1 -1
- package/skills/aimship/references/stages/scope.md +1 -1
- package/skills/aimship/references/stages/tech-design.md +1 -1
- package/skills/aimship/references/stages/test-design.md +1 -1
- package/skills/aimship/references/stages/verify.md +2 -2
- package/skills/aimship/references/templates/change-log.md +0 -2
- package/skills/aimship/references/templates/diagnosis.md +0 -7
- package/skills/aimship/references/templates/handoff.md +0 -6
- package/skills/aimship/references/templates/plan.md +2 -8
- package/skills/aimship/references/templates/prd.md +0 -7
- package/skills/aimship/references/templates/review.md +0 -3
- package/skills/aimship/references/templates/scope.md +0 -7
- package/skills/aimship/references/templates/tech-design.md +0 -7
- package/skills/aimship/references/templates/testing.md +2 -8
- package/lib/migrate.mjs +0 -77
package/lib/state.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { validateInitLocation } from './location.mjs';
|
|
2
|
+
import { text } from './language.mjs';
|
|
1
3
|
import { validateDelivery, arrangeSessions, completionStatus, sessionAdvice } from './delivery.mjs';
|
|
2
4
|
import fs from 'node:fs/promises';
|
|
3
5
|
import { validateReviewState, enforceReviews, reviewStatus } from './approvals.mjs';
|
|
4
6
|
import path from 'node:path';
|
|
5
|
-
import { inspectDocuments, documentChanges,
|
|
7
|
+
import { inspectDocuments, documentChanges, hasLocator, artifactMap, safeRead } from './documents.mjs';
|
|
6
8
|
import { savedResult, viewStatus, generateView } from './view.mjs';
|
|
7
9
|
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { atomicWrite, exists, fail, inside, json, noSymlinks, readJSON, withLock } from './io.mjs';
|
|
10
|
+
import { atomicWrite, exists, fail, hash, inside, json, noSymlinks, readJSON, withLock } from './io.mjs';
|
|
9
11
|
|
|
10
|
-
export const SCHEMA_VERSION =
|
|
12
|
+
export const SCHEMA_VERSION = 4;
|
|
11
13
|
export const STAGES = ['frame', 'requirements', 'prototype', 'scope', 'diagnose', 'tech-design', 'contract', 'test-design', 'plan', 'implement', 'review', 'verify', 'handoff'];
|
|
12
14
|
const PROGRESS = ['planned', 'active', 'blocked', 'done'];
|
|
13
15
|
const RESULTS = ['passed', 'failed', 'not-run', 'unknown', 'mixed'];
|
|
@@ -28,12 +30,14 @@ function unique(items, label) { requireThat(items.every(object), `${label} entri
|
|
|
28
30
|
|
|
29
31
|
export function validateState(state) {
|
|
30
32
|
requireThat(object(state), 'State must be an object.');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
requireThat(['en','zh-CN'].includes(state.language), 'language must be en or zh-CN.');
|
|
34
|
+
requireThat(state.schemaVersion === SCHEMA_VERSION, 'Invalid schemaVersion.');
|
|
35
|
+
requireThat(state.reviewPolicy===1 && state.deliveryPolicy?.version===1,'Current document and delivery approval policies are required.');
|
|
36
|
+
if (state.schemaVersion === SCHEMA_VERSION) {
|
|
37
|
+
requireThat(object(state.documentPolicy) && (['authored','adopted','editable'].includes(state.documentPolicy.authority) || (state.schemaVersion === 4 && state.documentPolicy.authority === null)), 'documentPolicy.authority is required.');
|
|
38
|
+
{
|
|
35
39
|
const p = state.documentPolicy;
|
|
36
|
-
const unknown = value => state.schemaVersion ===
|
|
40
|
+
const unknown = value => state.schemaVersion === 4 && value === null;
|
|
37
41
|
requireThat((unknown(p.interaction) || typeof p.interaction === 'boolean') && (unknown(p.prototypeCovered) || typeof p.prototypeCovered === 'boolean') && (unknown(p.interface) || ['none','http','other'].includes(p.interface)) && (unknown(p.databases) || (Array.isArray(p.databases) && p.databases.every(x => typeof x === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(x)) && new Set(p.databases).size === p.databases.length)), 'Invalid documentPolicy conditions.');
|
|
38
42
|
requireThat(Array.isArray(state.documents), 'documents must be an array.');
|
|
39
43
|
const paths = new Set();
|
|
@@ -47,9 +51,8 @@ export function validateState(state) {
|
|
|
47
51
|
}
|
|
48
52
|
requireThat(Number.isSafeInteger(state.revision) && state.revision >= 0, 'revision must be a nonnegative integer.');
|
|
49
53
|
requireThat(string(state.deliveryId) && string(state.rulesVersion), 'deliveryId and rulesVersion are required.');
|
|
50
|
-
requireThat(object(state.goal) && string(state.goal.summary) && (string(state.goal.endpoint) || (state.schemaVersion ===
|
|
54
|
+
requireThat(object(state.goal) && string(state.goal.summary) && (string(state.goal.endpoint) || (state.schemaVersion === 4 && state.goal.endpoint === null)), 'goal.summary and goal.endpoint are required.');
|
|
51
55
|
ref(state.goalRef, 'goalRef');
|
|
52
|
-
if (state.compatibilityRef !== undefined) ref(state.compatibilityRef, 'compatibilityRef');
|
|
53
56
|
if (state.goal.decisionRef !== undefined) ref(state.goal.decisionRef, 'goal.decisionRef');
|
|
54
57
|
requireThat(Number.isSafeInteger(state.cycle) && state.cycle >= 1, 'cycle must be a positive integer.');
|
|
55
58
|
refs(state.closureRefs, 'closureRefs');
|
|
@@ -77,58 +80,47 @@ export function validateState(state) {
|
|
|
77
80
|
requireThat(object(state.next) && string(state.next.action), 'next.action is required.');
|
|
78
81
|
if (state.next.inputRefs !== undefined) refs(state.next.inputRefs, 'next.inputRefs');
|
|
79
82
|
if (state.goalHistory !== undefined) requireThat(Array.isArray(state.goalHistory), 'goalHistory must be an array.');
|
|
80
|
-
if (state.rulesHistory !== undefined) requireThat(Array.isArray(state.rulesHistory), 'rulesHistory must be an array.');
|
|
81
83
|
validateDelivery(state);
|
|
82
84
|
if (state.deliveryPolicy?.partialRef) ref(state.deliveryPolicy.partialRef, 'deliveryPolicy.partialRef');
|
|
83
85
|
validateReviewState(state);
|
|
84
86
|
return state;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
export function statePaths(directory
|
|
88
|
-
const dir = path.resolve(directory), internal =
|
|
89
|
-
return { dir, internal, statePath:path.join(internal,'state.json'), backup:path.join(internal,'state.previous.json'), lock:path.join(internal,'.state.lock')
|
|
89
|
+
export function statePaths(directory) {
|
|
90
|
+
const dir = path.resolve(directory), internal = path.join(dir,'.aimship');
|
|
91
|
+
return { dir, internal, statePath:path.join(internal,'state.json'), backup:path.join(internal,'state.previous.json'), lock:path.join(internal,'.state.lock') };
|
|
90
92
|
}
|
|
91
|
-
export async function locateState(directory
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const journal=await exists(journalPath) ? await readJSON(journalPath) : null;
|
|
96
|
-
if (hasNew && hasOld) {
|
|
97
|
-
if (writing) fail('LAYOUT_CONFLICT','Both state layouts exist.','Inspect both copies; resume a recorded migration explicitly.');
|
|
98
|
-
return {...modern,conflict:true,candidates:[legacy.statePath,modern.statePath],migration:journal};
|
|
99
|
-
}
|
|
100
|
-
if (writing && journal && journal.status !== 'complete') fail('MIGRATION_PENDING','Migration is incomplete.','Resume migrate with the original revision and compatibilityRef.');
|
|
101
|
-
return {...(hasOld || (!hasNew && await exists(legacy.backup)) ? legacy : modern),migration:journal};
|
|
93
|
+
export async function locateState(directory) {
|
|
94
|
+
const current=statePaths(directory);
|
|
95
|
+
await noSymlinks(current.statePath);
|
|
96
|
+
return current;
|
|
102
97
|
}
|
|
103
98
|
export async function readState(directory) {
|
|
104
|
-
const paths=await locateState(directory)
|
|
99
|
+
const paths=await locateState(directory), state=await readJSON(paths.statePath);
|
|
105
100
|
requireThat(object(state),'State must be an object.');
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const review=readOnly ? null : await reviewStatus(directory,state);
|
|
110
|
-
const view=await viewStatus(directory,state), changes=await documentChanges(directory,state);
|
|
111
|
-
return {...paths,state,readOnly,layoutMismatch,review,completion:review ? completionStatus(state,review,{changed:changes}) : null,session:review ? sessionAdvice(directory,state,review,{saved:view.status==='current' && !changes.length}) : null,view,documentChanges:changes};
|
|
101
|
+
validateState(state);
|
|
102
|
+
const review=await reviewStatus(directory,state), view=await viewStatus(directory,state), changes=await documentChanges(directory,state);
|
|
103
|
+
return {...paths,state,readOnly:false,review,completion:completionStatus(state,review,{changed:changes}),session:sessionAdvice(directory,state,review,{saved:view.status==='current' && !changes.length}),view,documentChanges:changes};
|
|
112
104
|
}
|
|
113
105
|
async function inspectForWrite(directory,state,previous) {
|
|
114
106
|
const inspected=await inspectDocuments(directory,state);
|
|
115
107
|
if (inspected.issues.length) fail('DOCUMENT_CHECK','Document structure or traceability needs repair.','Repair the specific documents; file presence is not semantic completion.',inspected);
|
|
116
|
-
|
|
117
|
-
const legacyRuns=await preservedLegacyRuns(directory,state);
|
|
118
|
-
for (const run of state.stageRuns.filter(r=>r.progress==='done' && !legacyRuns.has(r.id))) {
|
|
108
|
+
for (const run of state.stageRuns.filter(r=>r.progress==='done')) {
|
|
119
109
|
if (previous?.stageRuns.some(r=>r.id===run.id && JSON.stringify(r)===JSON.stringify(run))) continue;
|
|
120
110
|
const results=await checkReferences(directory,{...state,goalRef:run.resultRef,next:{inputRefs:[]},stageRuns:[],currentRun:null});
|
|
121
111
|
if (results.some(r=>!['exists','not-checked'].includes(r.status))) fail('DOCUMENT_CHECK',`Cannot locate result for ${run.id}.`,'Fix the result reference.',{references:results});
|
|
122
112
|
}
|
|
113
|
+
const original=previous?.documentDigests?.['source/request.md'];
|
|
114
|
+
if (original && inspected.digests['source/request.md'] === undefined) {
|
|
115
|
+
try { inspected.digests['source/request.md']=hash(await safeRead(directory,state,'source/request.md')); } catch { inspected.digests['source/request.md']=null; }
|
|
116
|
+
}
|
|
117
|
+
if (original && inspected.digests['source/request.md'] !== original) fail('SOURCE_CHANGED','The preserved original request changed.','Restore source/request.md from the original evidence; record revised requirements in the current authority and change log. An unrelated save cannot adopt changed original input.');
|
|
123
118
|
state.documentDigests=inspected.digests;
|
|
124
|
-
state.retiredIdentities=[...new Set([...(previous?.retiredIdentities??[]),...inspected.retired??[]])];
|
|
125
|
-
state.identityHistory=[...new Set([...(previous?.identityHistory??[]),...inspected.identities])];
|
|
126
119
|
}
|
|
127
120
|
export async function renderState(directory,revision,rulesVersion,runtime,preserveEdits=false) {
|
|
128
121
|
const p=await locateState(directory,true);
|
|
129
122
|
return withLock(p.lock,async()=>{
|
|
130
123
|
const {state}=await readState(directory); validateState(state); checkRevision(state,revision); assertRules(state,rulesVersion,runtime);
|
|
131
|
-
if (p.legacy) fail('MIGRATION_REQUIRED','Migrate before rendering.','Use migrate, then adopt.');
|
|
132
124
|
return {revision:state.revision,stateSaved:false,view:await generateView(directory,state,preserveEdits)};
|
|
133
125
|
});
|
|
134
126
|
}
|
|
@@ -137,7 +129,7 @@ function checkRevision(state, revision) {
|
|
|
137
129
|
}
|
|
138
130
|
export function assertRules(state, rulesVersion, runtime) {
|
|
139
131
|
assertInstallationComplete(runtime);
|
|
140
|
-
if (!rulesVersion || rulesVersion !== state.rulesVersion || rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version) fail('VERSION_MISMATCH', 'The session, state, tool, and active installation must use the same rules version.', '
|
|
132
|
+
if (!rulesVersion || rulesVersion !== state.rulesVersion || rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version) fail('VERSION_MISMATCH', 'The session, state, tool, and active installation must use the same rules version.', 'Bind the active version and reload its core and current stage; do not write with mismatched state, session or tool versions.', { session: rulesVersion, state: state.rulesVersion, tool: runtime.version, active: runtime.activeVersion, launcher: runtime.launcher });
|
|
141
133
|
}
|
|
142
134
|
export function assertInstallationComplete(runtime) {
|
|
143
135
|
if (runtime.installPending) fail('INSTALL_PENDING', 'The installation is incomplete; new workflow writes and bindings are suspended.', 'Read state without progressing, then rerun install from the same exact pending package. After recovery, bind and reload its fixed core before continuing.', { pendingVersion: runtime.installPending.version, activeVersion: runtime.activeVersion, launcher: runtime.launcher });
|
|
@@ -145,17 +137,20 @@ export function assertInstallationComplete(runtime) {
|
|
|
145
137
|
export async function initState(directory, input, rulesVersion, runtime) {
|
|
146
138
|
assertInstallationComplete(runtime);
|
|
147
139
|
if (rulesVersion !== runtime.version || runtime.activeVersion !== runtime.version) fail('VERSION_MISMATCH', 'New delivery requires the active tool and rules version.', 'Bind the current installation and use its returned version.');
|
|
148
|
-
const p = statePaths(directory); await noSymlinks(p.internal);
|
|
149
|
-
if (await exists(
|
|
140
|
+
const p = statePaths(directory); await noSymlinks(p.internal);
|
|
141
|
+
if (await exists(p.statePath) || await exists(p.backup)) fail('ALREADY_EXISTS','Existing delivery or recovery material found.','Read/resume the existing delivery.');
|
|
142
|
+
requireThat(object(input) && ['en','zh-CN'].includes(input.language), 'New delivery input requires language en or zh-CN, from the explicit preference or current user conversation.');
|
|
143
|
+
const storage = await validateInitLocation(directory,input,runtime);
|
|
144
|
+
await fs.mkdir(p.internal, { recursive: true });
|
|
150
145
|
return withLock(p.lock, async () => {
|
|
151
146
|
if (await exists(p.statePath)) fail('ALREADY_EXISTS', 'A delivery already exists here.', 'Read/resume this delivery or choose a different folder.');
|
|
152
|
-
const forbidden = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', '
|
|
147
|
+
const forbidden = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult'];
|
|
153
148
|
requireThat(object(input) && !forbidden.some(x => Object.hasOwn(input, x)), 'init input must not set tool-managed identity/version fields.');
|
|
154
149
|
const now = new Date().toISOString();
|
|
155
|
-
const state = { goalRef:'source/request.md', documentPolicy:{authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null}, documents:[], deliveryPolicy: {version:1}, reviewPolicy: 1, reviewHistory: [], cycle: 1, closureRefs: [], stageRuns: [], currentRun: null, openChanges: [], repositories: [], outcome: { goal: 'in-progress', acceptance: 'not-recorded' }, next: { action: 'Clarify the request and prepare requirements for review.', inputRefs:['source/request.md'] }, ...input, schemaVersion: SCHEMA_VERSION, revision: 0, deliveryId: randomUUID(), rulesVersion, createdAt: now, updatedAt: now };
|
|
150
|
+
const state = { goalRef:'source/request.md', documentPolicy:{authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null}, documents:[], deliveryPolicy: {version:1}, reviewPolicy: 1, reviewHistory: [], cycle: 1, closureRefs: [], stageRuns: [], currentRun: null, openChanges: [], repositories: [], outcome: { goal: 'in-progress', acceptance: 'not-recorded' }, next: { action: text(input,'Clarify the request and prepare requirements for review.','澄清诉求并准备需求审核。'), inputRefs:['source/request.md'] }, ...input, storage, schemaVersion: SCHEMA_VERSION, revision: 0, deliveryId: randomUUID(), rulesVersion, createdAt: now, updatedAt: now };
|
|
156
151
|
if (object(input.documentPolicy)) state.documentPolicy = {authority:null,interaction:null,prototypeCovered:null,interface:null,databases:null,...input.documentPolicy};
|
|
157
152
|
if (object(state.goal) && state.goal.endpoint === undefined) state.goal.endpoint = null;
|
|
158
|
-
arrangeSessions(state); validateState(state); requireThat(state.deliveryPolicy?.version === 1, 'New deliveries require deliveryPolicy version 1.');
|
|
153
|
+
arrangeSessions(state); validateState(state); requireThat(state.deliveryPolicy?.version === 1, 'New deliveries require deliveryPolicy version 1.'); await inspectForWrite(directory,state); await enforceReviews(directory,state); await enforceCompletion(directory,state); await atomicWrite(p.statePath, json(state)); return savedResult(directory,state);
|
|
159
154
|
});
|
|
160
155
|
}
|
|
161
156
|
function merge(base, patch) {
|
|
@@ -182,56 +177,30 @@ function retainHistory(previous, next) {
|
|
|
182
177
|
requireThat(next.cycle >= previous.cycle && next.cycle <= previous.cycle + 1, 'cycle may stay unchanged or increase by one.');
|
|
183
178
|
if (next.cycle > previous.cycle) requireThat(next.closureRefs.length > previous.closureRefs.length && next.outcome.acceptance === 'not-recorded', 'Reopening requires a previous closure reference and reset current acceptance.');
|
|
184
179
|
}
|
|
185
|
-
export async function updateState(directory, patch, revision, rulesVersion, runtime
|
|
180
|
+
export async function updateState(directory, patch, revision, rulesVersion, runtime) {
|
|
186
181
|
assertInstallationComplete(runtime);
|
|
187
182
|
const p = await locateState(directory,true);
|
|
188
183
|
return withLock(p.lock, async () => {
|
|
189
184
|
const read = await readState(directory);
|
|
190
|
-
if (read.layoutMismatch) fail('LAYOUT_CONFLICT','Schema and layout disagree.','Inspect the original layout; do not write through an inconsistent path.');
|
|
191
185
|
const previous = read.state; validateState(previous); checkRevision(previous, revision);
|
|
192
|
-
if (previous.schemaVersion === 1 && mode !== 'checkpoint') fail('MIGRATION_REQUIRED','Legacy layout/schema must migrate before rule adoption or updates.','Use migrate with a compatibility assessment, then adopt.');
|
|
193
186
|
requireThat(object(patch), 'Update input must be an object.');
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
requireThat(Object.keys(patch).every(x => ['next', 'checkpoint'].includes(x)) && object(patch.checkpoint) && string(patch.checkpoint.summary), 'Checkpoint may save only next and checkpoint.summary/ref facts; it cannot complete stages or outcomes.');
|
|
197
|
-
if (runtime.activeVersion === runtime.version) fail('CHECKPOINT_NOT_NEEDED', 'Rules match; use a normal update for unfinished facts.', 'Save current work with update.');
|
|
198
|
-
if (previous.checkpoint?.activeVersion === runtime.activeVersion) fail('CHECKPOINT_ALREADY_SAVED', 'A checkpoint for this active version already exists.', 'Reload active rules and adopt at the recorded stable point.');
|
|
199
|
-
if (patch.checkpoint.ref !== undefined) ref(patch.checkpoint.ref, 'checkpoint.ref');
|
|
200
|
-
} else if (mode === 'adopt') {
|
|
201
|
-
requireThat(runtime.version === runtime.activeVersion && rulesVersion === runtime.version, 'Adoption requires the active rules and tool.');
|
|
202
|
-
requireThat(previous.rulesVersion !== rulesVersion, 'Rules are already adopted; no version change is needed.');
|
|
203
|
-
requireThat(Object.keys(patch).length === 1 && string(patch.compatibilityRef), 'Adoption input must contain only compatibilityRef documenting the stable point and compatibility assessment.');
|
|
204
|
-
ref(patch.compatibilityRef, 'compatibilityRef');
|
|
205
|
-
} else {
|
|
206
|
-
assertRules(previous, rulesVersion, runtime);
|
|
207
|
-
}
|
|
208
|
-
const immutable = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'rulesHistory', 'documentDigests', 'identityHistory', 'retiredIdentities', 'legacyRunIds', 'writeResult'];
|
|
187
|
+
assertRules(previous, rulesVersion, runtime);
|
|
188
|
+
const immutable = ['schemaVersion', 'revision', 'deliveryId', 'rulesVersion', 'createdAt', 'updatedAt', 'goalHistory', 'documentDigests', 'writeResult', 'storage'];
|
|
209
189
|
requireThat(!immutable.some(x => Object.hasOwn(patch, x)), 'Identity, revision, and historical metadata are tool-managed.');
|
|
210
|
-
|
|
211
|
-
if (mode === 'adopt') {
|
|
212
|
-
next = { ...previous, rulesVersion, rulesHistory: [...(previous.rulesHistory ?? []), { from: previous.rulesVersion, to: rulesVersion, ref: patch.compatibilityRef, at: new Date().toISOString() }] };
|
|
213
|
-
} else next = merge(previous, patch);
|
|
214
|
-
if (mode === 'checkpoint') next.checkpoint.activeVersion = runtime.activeVersion;
|
|
190
|
+
const next = merge(previous, patch);
|
|
215
191
|
requireThat(previous.deliveryPolicy === undefined || next.deliveryPolicy?.version === previous.deliveryPolicy.version, 'Cannot remove or downgrade deliveryPolicy.');
|
|
216
|
-
if (previous.deliveryPolicy === undefined && next.deliveryPolicy !== undefined) {
|
|
217
|
-
requireThat(string(next.compatibilityRef) && patch.compatibilityRef !== undefined, 'Explicit delivery-policy adoption needs compatibilityRef; preserve historical outcomes.');
|
|
218
|
-
const basis = await checkReferences(directory,{...next,goalRef:next.compatibilityRef,next:{inputRefs:[]},stageRuns:[],currentRun:null});
|
|
219
|
-
requireThat(basis.every(r=>['exists','not-checked'].includes(r.status)), 'Compatibility assessment must be accessible before policy adoption.');
|
|
220
|
-
}
|
|
221
192
|
arrangeSessions(next); validateState(next);
|
|
222
193
|
// Registering the first formal basis is not a new user scope decision.
|
|
223
|
-
const formalizing = previous.schemaVersion ===
|
|
194
|
+
const formalizing = previous.schemaVersion === 4 && previous.goalRef === 'source/request.md' && !previous.documents.some(d=>['prd','scope'].includes(d.kind)) && next.documents.some(d=>['prd','scope'].includes(d.kind) && d.path === next.goalRef.split('#')[0]);
|
|
224
195
|
if (JSON.stringify(previous.goal) !== JSON.stringify(next.goal) || (previous.goalRef !== next.goalRef && !formalizing)) {
|
|
225
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.');
|
|
226
197
|
next.goalHistory = [...(previous.goalHistory ?? []), { goal: previous.goal, goalRef: previous.goalRef, deliveryPolicy: previous.deliveryPolicy, outcome: previous.outcome, revision: previous.revision }];
|
|
227
198
|
}
|
|
228
|
-
|
|
229
|
-
if (mode === 'update' && next.documentPolicy?.authority === 'legacy') requireThat(Object.keys(patch).every(k=>['next','checkpoint','repositories'].includes(k)), 'Reconcile legacy documents and set the current documentPolicy before advancing stages or outcomes.');
|
|
230
|
-
if (mode === 'update') { await inspectForWrite(directory,next,previous); if (next.reviewPolicy === 1 || !Object.keys(patch).every(k=>['next','checkpoint','repositories'].includes(k))) await enforceReviews(directory,next,previous); }
|
|
199
|
+
await inspectForWrite(directory,next,previous); await enforceReviews(directory,next,previous);
|
|
231
200
|
if (previous.deliveryPolicy?.partialRef !== next.deliveryPolicy?.partialRef && previous.deliveryPolicy !== undefined) requireThat(string(next.goal.decisionRef) && next.goal.decisionRef !== previous.goal.decisionRef, 'Scope changes need a new explicit goal decision.');
|
|
232
|
-
if (
|
|
201
|
+
if (next.deliveryPolicy && previous.deliveryPolicy && JSON.stringify(previous.outcome)!==JSON.stringify(next.outcome)) await enforceCompletion(directory,next);
|
|
233
202
|
retainHistory(previous, next); next.revision = previous.revision + 1; next.updatedAt = new Date().toISOString();
|
|
234
|
-
validateState(next); await atomicWrite(p.backup, json(previous)); await atomicWrite(p.statePath, json(next)); return
|
|
203
|
+
validateState(next); await atomicWrite(p.backup, json(previous)); await atomicWrite(p.statePath, json(next)); return savedResult(directory,next);
|
|
235
204
|
});
|
|
236
205
|
}
|
|
237
206
|
export async function recoverState(directory, revision, rulesVersion, runtime) {
|
|
@@ -243,8 +212,8 @@ export async function recoverState(directory, revision, rulesVersion, runtime) {
|
|
|
243
212
|
const raw = await fs.readFile(p.statePath, 'utf8');
|
|
244
213
|
let parsed; try { parsed = JSON.parse(raw); } catch { /* Corruption is preserved below. */ }
|
|
245
214
|
if (parsed) {
|
|
246
|
-
|
|
247
|
-
|
|
215
|
+
requireThat(parsed.deliveryPolicy?.version === backup.deliveryPolicy.version && parsed.reviewPolicy === backup.reviewPolicy, 'State and recovery copy must use the same policies.');
|
|
216
|
+
requireThat(parsed.schemaVersion === backup.schemaVersion, 'State and recovery copy must use the same schemaVersion.');
|
|
248
217
|
try { validateState(parsed); fail('STATE_VALID', 'Current state is valid; refusing rollback.', 'Use a normal revision-checked update to reconcile facts.'); }
|
|
249
218
|
catch (e) { if (!['INVALID_STATE','REVIEW_CHECK'].includes(e.code)) throw e; }
|
|
250
219
|
if (Number.isSafeInteger(parsed.revision)) corruptRevision = Math.max(corruptRevision, parsed.revision);
|
|
@@ -252,7 +221,7 @@ export async function recoverState(directory, revision, rulesVersion, runtime) {
|
|
|
252
221
|
await atomicWrite(path.join(p.internal, `state.corrupt.${randomUUID()}.json`), raw);
|
|
253
222
|
}
|
|
254
223
|
const next = { ...backup, revision: corruptRevision + 1, updatedAt: new Date().toISOString(), recovery: { fromRevision: backup.revision, action: 'Inspect actual artifacts and unfinished effects before proceeding; nothing has been re-executed.' } };
|
|
255
|
-
await atomicWrite(p.statePath, json(next)); return
|
|
224
|
+
await atomicWrite(p.statePath, json(next)); return savedResult(directory,next);
|
|
256
225
|
});
|
|
257
226
|
}
|
|
258
227
|
export async function checkReferences(directory, state, all = false) {
|
|
@@ -264,13 +233,11 @@ export async function checkReferences(directory, state, all = false) {
|
|
|
264
233
|
const add = value => { if (value !== undefined) { ref(value, 'registered reference'); values.add(value); } };
|
|
265
234
|
const addOutcome = outcome => { for (const key of OUTCOME_REFS) add(outcome?.[key]); };
|
|
266
235
|
if (state.deliveryPolicy?.partialRef != null) add(state.deliveryPolicy.partialRef);
|
|
267
|
-
add(state.
|
|
236
|
+
add(state.storage?.decisionRef);
|
|
268
237
|
add(state.goal.decisionRef); addOutcome(state.outcome);
|
|
269
238
|
for (const history of state.goalHistory ?? []) {
|
|
270
239
|
add(history?.goalRef); add(history?.goal?.decisionRef); addOutcome(history?.outcome);
|
|
271
240
|
}
|
|
272
|
-
for (const history of state.rulesHistory ?? []) add(history?.ref);
|
|
273
|
-
add(state.checkpoint?.ref);
|
|
274
241
|
for (const r of state.reviewHistory ?? []) add(r.sourceRef);
|
|
275
242
|
for (const d of state.documents ?? []) for (const r of d.assetRefs ?? []) add(r);
|
|
276
243
|
}
|
|
@@ -292,8 +259,8 @@ export async function checkReferences(directory, state, all = false) {
|
|
|
292
259
|
await fs.access(real, fs.constants.R_OK);
|
|
293
260
|
const fragment=value.split('#')[1], doc=state.documents?.find(d=>d.path===value.split('#')[0]);
|
|
294
261
|
if (fragment && doc?.mode==='new' && /\.md$/.test(relative)) {
|
|
295
|
-
const
|
|
296
|
-
results.push({ref:value,status:
|
|
262
|
+
const body=await fs.readFile(real,'utf8');
|
|
263
|
+
results.push({ref:value,status:hasLocator(body,fragment,(await artifactMap()).documents[doc.kind])?'exists':'missing-fragment',path:target,fragmentChecked:true});
|
|
297
264
|
} else if (fragment) results.push({ref:value,status:'not-checked',path:target,fragmentChecked:false,reason:'Nonstandard/reused fragment requires host inspection and recorded basis.'});
|
|
298
265
|
else results.push({ref:value,status:'exists',path:target,fragmentChecked:false});
|
|
299
266
|
}
|
|
@@ -304,7 +271,7 @@ export async function checkReferences(directory, state, all = false) {
|
|
|
304
271
|
|
|
305
272
|
async function enforceCompletion(directory,state) {
|
|
306
273
|
if (state.deliveryPolicy?.version !== 1 || state.outcome.goal !== 'met') return;
|
|
307
|
-
if (state.schemaVersion ===
|
|
274
|
+
if (state.schemaVersion === 4 && (state.goal.endpoint === null || state.goalRef === 'source/request.md')) fail('DELIVERY_INCOMPLETE','Initialization is not a completed delivery.','Resolve the endpoint and formal requirements before claiming completion.');
|
|
308
275
|
const completion=completionStatus(state,await reviewStatus(directory,state));
|
|
309
276
|
if (!state.deliveryPolicy.partialRef && !completion.readyForAcceptance) fail('DELIVERY_INCOMPLETE','Standard delivery evidence is incomplete.','Keep the goal in progress, or record the explicitly limited scope without claiming standard completion.',completion);
|
|
310
277
|
const refs=await checkReferences(directory,state,true);
|
package/lib/view.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { text, displayValue } from './language.mjs';
|
|
1
2
|
import { completionStatus, sessionAdvice } from './delivery.mjs';
|
|
2
3
|
import fs from 'node:fs/promises';
|
|
3
4
|
import { reviewStatus } from './approvals.mjs';
|
|
@@ -8,55 +9,56 @@ import { resolveRef, documentChanges } from './documents.mjs';
|
|
|
8
9
|
|
|
9
10
|
const paths = dir => ({ view: path.join(dir,'status.md'), meta: path.join(dir,'.aimship/view.json') });
|
|
10
11
|
export async function viewStatus(directory, state) {
|
|
11
|
-
|
|
12
|
+
const t=(en,zh)=>text(state,en,zh);
|
|
12
13
|
const p=paths(directory);
|
|
13
14
|
try {
|
|
14
|
-
if (!await exists(p.view)) return { status:'missing', recovery:'Run render at the current revision.' };
|
|
15
|
+
if (!await exists(p.view)) return { status:'missing', recovery:t('Run render at the current revision.','使用当前修订重新生成视图。') };
|
|
15
16
|
await noSymlinks(p.view); await noSymlinks(p.meta);
|
|
16
|
-
if (!await exists(p.meta)) return { status:'modified', recovery:'No trusted generation digest; preserve edits with render --preserve-edits.' };
|
|
17
|
+
if (!await exists(p.meta)) return { status:'modified', recovery:t('No trusted generation digest; preserve edits with render --preserve-edits.','没有可信的生成摘要;请保留手工修改后重新生成视图。') };
|
|
17
18
|
const meta=await readJSON(p.meta), raw=await fs.readFile(p.view,'utf8');
|
|
18
|
-
if (hash(raw)!==meta.hash) return { status:'modified', recovery:'Inspect the edits, then render --preserve-edits.' };
|
|
19
|
+
if (hash(raw)!==meta.hash) return { status:'modified', recovery:t('Inspect the edits, then render --preserve-edits.','先核对修改,再保留手工内容并重新生成视图。') };
|
|
19
20
|
const current=meta.deliveryId===state.deliveryId && meta.revision===state.revision;
|
|
20
|
-
return { status:current?'current':'stale', revision:meta.revision, recovery:current?null:'Run render; do not replay the saved business update.' };
|
|
21
|
-
} catch(error) { return {status:'error',reason:error.code ?? error.message,recovery:'Inspect view/metadata paths; preserve untrusted content before render.'}; }
|
|
21
|
+
return { status:current?'current':'stale', revision:meta.revision, recovery:current?null:t('Run render; do not replay the saved business update.','重新生成视图,不要重放已保存的业务更新。') };
|
|
22
|
+
} catch(error) { return {status:'error',reason:error.code ?? error.message,recovery:t('Inspect view/metadata paths; preserve untrusted content before render.','核对视图和元数据路径,重新生成前保留现有内容。')}; }
|
|
22
23
|
}
|
|
23
24
|
const cell = x => String(x ?? 'not recorded').replaceAll('|','\\|').replaceAll('\n',' ');
|
|
24
|
-
const link = ref => `[${ref.replaceAll('[','\\[').replaceAll(']','\\]')}](${ref.replaceAll(' ','%20').replaceAll('(','%28').replaceAll(')','%29')})`;
|
|
25
|
+
const link = ref => `[${ref.replaceAll('[','\\[').replaceAll(']','\\]')}](${ref.split('#')[0].replaceAll(' ','%20').replaceAll('(','%28').replaceAll(')','%29')})`;
|
|
25
26
|
async function available(directory,state,ref) {
|
|
26
27
|
if (/^https?:\/\//.test(ref)) return true;
|
|
27
28
|
const p=resolveRef(directory,state,ref); return p && await exists(p);
|
|
28
29
|
}
|
|
29
30
|
export async function renderText(directory,state) {
|
|
31
|
+
const t=(en,zh)=>text(state,en,zh), v=x=>cell(displayValue(state,x)), identifier=x=>x==null?v(null):cell(x);
|
|
30
32
|
const artifactLink=ref=> {
|
|
31
33
|
if (!ref.startsWith('repo:')) return link(ref);
|
|
32
|
-
const target=resolveRef(directory,state,ref);
|
|
33
|
-
|
|
34
|
-
return target ? `[${cell(ref)}](${encodeURI(target).replaceAll('(','%28').replaceAll(')','%29')}${fragment})` : cell(ref);
|
|
34
|
+
const target=resolveRef(directory,state,ref), fragment=ref.includes('#')?'#'+ref.split('#')[1]:'';
|
|
35
|
+
return target ? `[${cell(ref)}](${encodeURI(target).replaceAll('(','%28').replaceAll(')','%29')})` : cell(ref);
|
|
35
36
|
};
|
|
36
|
-
const lines=['# Delivery status','','Generated by Aimship. Edit formal documents and structured state, not this page.','',
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
lines.push(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
lines.push('
|
|
43
|
-
|
|
44
|
-
lines.push(
|
|
45
|
-
|
|
46
|
-
for
|
|
47
|
-
|
|
48
|
-
const review
|
|
49
|
-
lines.push('',
|
|
50
|
-
for
|
|
51
|
-
for
|
|
52
|
-
lines.push('',
|
|
53
|
-
const completion=completionStatus(state,review,{changed:await documentChanges(directory,state)}),
|
|
54
|
-
lines.push('',
|
|
55
|
-
if
|
|
56
|
-
lines.push('',
|
|
37
|
+
const lines=[t('# Delivery status','# 交付状态'),'',t('Generated by Aimship. Edit formal documents and structured state, not this page.','由 Aimship 生成。请通过正式文档和结构化状态更新工作记录。'),'',t('## Goal and basis','## 目标与依据'),'',cell(state.goal.summary),'',`${t('Endpoint','交付终点')}: ${cell(state.goal.endpoint ?? t('Unknown — not yet agreed','未知,尚未约定'))}`,''];
|
|
38
|
+
lines.push(`${t('Display language','显示语言')}: ${state.language ?? v(null)}`,'');
|
|
39
|
+
if (state.schemaVersion===4) lines.push(t('Initialization: state saved; this page is the derived view.','初始化:状态已保存,本页为生成的视图。'),`${t('Requirements','需求')}: ${state.documents.some(d=>['prd','scope'].includes(d.kind))?t('registered; see document review','已登记,见文档审核'):t('being clarified; no formal document yet','正在澄清,尚无正式文档')}`,`${t('Artifact conditions','产物适用条件')}: ${['interaction','prototypeCovered','interface','databases'].map(k=>`${v(k)}=${state.documentPolicy[k]===null?v('unknown'):Array.isArray(state.documentPolicy[k])?(state.documentPolicy[k].length?state.documentPolicy[k].join(', '):v('none')):v(state.documentPolicy[k])}`).join(', ')}`,'');
|
|
40
|
+
if(await available(directory,state,state.goalRef))lines.push(artifactLink(state.goalRef),'');
|
|
41
|
+
lines.push(t('## Associated engineering objects','## 关联工程'),'',t('| ID | Actual checkout/worktree | Role |','| ID | 实际代码目录 | 用途 |'),'| --- | --- | --- |');
|
|
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)}`);
|
|
56
|
+
if(session.instruction)lines.push('',cell(session.instruction));
|
|
57
|
+
if(session.continuation)lines.push('',cell(session.continuation));
|
|
58
|
+
lines.push('',t('## Artifacts and plan','## 成果与计划'),'');
|
|
57
59
|
const refs=new Set([...(state.documents??[]).map(d=>d.path),...state.stageRuns.flatMap(r=>[...r.outputRefs,r.resultRef].filter(Boolean)),...(state.next.inputRefs??[])]);
|
|
58
|
-
for
|
|
59
|
-
lines.push('','## Independent outcomes and generation',''
|
|
60
|
+
for(const ref of refs)if(await available(directory,state,ref))lines.push(`- ${artifactLink(ref)}`);
|
|
61
|
+
lines.push('',t('## Independent outcomes and generation','## 独立结果与生成信息'),'',`${t('Goal','目标')}: ${v(state.outcome.goal)}`,`${t('Acceptance','用户接受')}: ${v(state.outcome.acceptance)}`,`${t('Merge','合并')}: ${state.outcome.mergeRef?link(state.outcome.mergeRef):v(null)}`,`${t('Deployment','部署')}: ${state.outcome.deploymentRef?link(state.outcome.deploymentRef):v(null)}`,'',`${t('Delivery','交付')}: ${state.deliveryId}`,`${t('State revision','状态修订')}: ${state.revision}`,`${t('Rules','规则版本')}: ${state.rulesVersion}`,`${t('State saved','状态保存时间')}: ${state.updatedAt}`,'',t('Matching revisions do not establish document semantics or authorization.','修订一致不代表文档语义或授权已经核实。'),'');
|
|
60
62
|
return lines.join('\n');
|
|
61
63
|
}
|
|
62
64
|
export async function generateView(directory,state,preserveEdits=false) {
|
|
@@ -73,7 +75,7 @@ export async function generateView(directory,state,preserveEdits=false) {
|
|
|
73
75
|
await atomicWrite(p.view,text);
|
|
74
76
|
await atomicWrite(p.meta,json({deliveryId:state.deliveryId,revision:state.revision,hash:hash(text)}));
|
|
75
77
|
return {status:'current',revision:state.revision,...(preservedPath?{preservedPath}:{})};
|
|
76
|
-
} catch(error) {return {status:'error',reason:error.code??error.message,...(preservedPath?{preservedPath}:{}),recovery:'State was saved. Repair the view paths, then render at the returned revision; do not replay update.'};}
|
|
78
|
+
} catch(error) {return {status:'error',reason:error.code??error.message,...(preservedPath?{preservedPath}:{}),recovery:text(state,'State was saved. Repair the view paths, then render at the returned revision; do not replay update.','状态已保存。请修复视图路径,并在返回的修订上重新生成视图;不要重复业务更新。')};}
|
|
77
79
|
}
|
|
78
80
|
export async function savedResult(directory,state) {
|
|
79
81
|
const view=await generateView(directory,state);
|
package/package.json
CHANGED
package/skills/aimship/SKILL.md
CHANGED
|
@@ -8,12 +8,12 @@ disable-model-invocation: true
|
|
|
8
8
|
|
|
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
|
-
1. Run `"{{AIMSHIP_NODE}}" "{{AIMSHIP_LAUNCHER}}" bind` to resolve the installed version. Read the returned fixed-version core and CLI help. 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.
|
|
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
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/Scope 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
|
-
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 or known technical conditions are required. Preserve the original request, resolve the same delivery and access, and use
|
|
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
14
|
4. Read the selected [stage reference](references/core.md#stage-references) when reaching it. Explain design choices and unresolved decisions. Follow [document review](references/document-review.md): generate, self-check, give the current document entry/status/review focus and wait; do not send a next-stage continuation prompt while review is pending. After explicit approval save and verify the record, then follow the core session interaction: continue within a group, suggest a new session at its boundary without executing the next group in that response; continuing here needs no new approval; questions, silence and viewing results do not approve. Authorized implementation/review/repair/retest can then continue.
|
|
15
15
|
5. Use the core’s default session groups in existing state. At confirmed saved group boundaries give current results, next suggestion and one continuation line with the actual delivery directory; no extra prompt document. A requested full prompt is optional. On resume, uniquely verify the delivery and actual saved review/unfinished-work stop; never infer approval from the copied entry. Keep a compact binding: delivery ID, absolute delivery directory, state path, current run, rules version, and fixed core/tool paths. After compaction, reload these references if absent; do not assume the full skill survived.
|
|
16
16
|
|
|
17
|
-
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; [
|
|
17
|
+
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
18
|
|
|
19
19
|
The agent handles meaning, engineering and evidence. The local tools protect structure and writes; a successful command or a file's existence cannot establish goal completion. This skill does not schedule agents, open conversations, merge, deploy, or execute database changes by default.
|