agent-orchestrator-kit 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +23 -4
- package/bin/agent-orchestrator.js +461 -114
- package/bin/claude-cost-estimate.js +51 -0
- package/bin/session-client.js +55 -6
- package/bin/spend-collect.js +79 -13
- package/package.json +1 -1
- package/templates/.agents/commands/opsx-propose.md +6 -0
- package/templates/.agents/commands/opsx-review.md +35 -11
- package/templates/.agents/rules/session-handoff.mdc +2 -2
- package/templates/.agents/skills/agent-orchestration/SKILL.md +5 -4
- package/templates/.agents/skills/openspec-propose/SKILL.md +6 -0
- package/templates/.agents/subagents/openspec-guide.md +3 -2
- package/templates/.agents/subagents/session-handoff.md +2 -1
- package/templates/.agents/subagents/spec-architect.md +6 -1
- package/templates/.agents/subagents/spec-archiver.md +2 -1
- package/templates/.agents/subagents/spec-reviewer.md +5 -1
- package/templates/AGENTS.md +1 -1
- package/templates/scripts/cursor-spend-collect.cjs +129 -60
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function numOrNull(value) {
|
|
2
|
+
if (value == null || value === '') return null;
|
|
3
|
+
const n = Number(value);
|
|
4
|
+
return Number.isFinite(n) ? n : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const RATES = {
|
|
8
|
+
'claude-fable-5': { input: 10, cacheRead: 0.25, cacheWrite: 12.5, output: 50 },
|
|
9
|
+
'claude-opus': { input: 5, cacheRead: 0.5, cacheWrite: 6.25, output: 25 },
|
|
10
|
+
'claude-sonnet-5': { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 10 },
|
|
11
|
+
'claude-sonnet-4-6': { input: 3, cacheRead: 0.3, cacheWrite: 3.75, output: 15 },
|
|
12
|
+
'claude-haiku-4-5': { input: 1, cacheRead: 0.1, cacheWrite: 1.25, output: 5 },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function ratesForModel(model) {
|
|
16
|
+
const id = String(model || '').toLowerCase();
|
|
17
|
+
return Object.entries(RATES)
|
|
18
|
+
.sort(([a], [b]) => b.length - a.length)
|
|
19
|
+
.find(([prefix]) => id.startsWith(prefix))?.[1] || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function estimateClaudeCostUsd({
|
|
23
|
+
model,
|
|
24
|
+
inputTokens,
|
|
25
|
+
cacheReadTokens,
|
|
26
|
+
cacheCreationTokens,
|
|
27
|
+
outputTokens,
|
|
28
|
+
} = {}) {
|
|
29
|
+
const input = numOrNull(inputTokens);
|
|
30
|
+
const cacheRead = numOrNull(cacheReadTokens);
|
|
31
|
+
const cacheWrite = numOrNull(cacheCreationTokens);
|
|
32
|
+
const output = numOrNull(outputTokens);
|
|
33
|
+
if (input == null && cacheRead == null && cacheWrite == null && output == null) return null;
|
|
34
|
+
const rates = ratesForModel(model);
|
|
35
|
+
const usd = rates
|
|
36
|
+
? ((input ?? 0) * rates.input
|
|
37
|
+
+ (cacheRead ?? 0) * rates.cacheRead
|
|
38
|
+
+ (cacheWrite ?? 0) * rates.cacheWrite
|
|
39
|
+
+ (output ?? 0) * rates.output) / 1e6
|
|
40
|
+
: (((input ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0)) * 3 + (output ?? 0) * 15) / 1e6;
|
|
41
|
+
return Math.round(usd * 10000) / 10000;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function describeClaudeCostEstimate(args = {}) {
|
|
45
|
+
const usd = estimateClaudeCostUsd(args);
|
|
46
|
+
if (usd == null) return null;
|
|
47
|
+
return {
|
|
48
|
+
usd,
|
|
49
|
+
costSource: ratesForModel(args.model) ? 'api-estimate' : 'api-estimate-fallback',
|
|
50
|
+
};
|
|
51
|
+
}
|
package/bin/session-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readlinkSync } from 'fs';
|
|
1
|
+
import { existsSync, readFileSync, readlinkSync, statSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir as osHomedir } from 'os';
|
|
4
4
|
import { execFileSync } from 'child_process';
|
|
@@ -114,28 +114,68 @@ function isFreshTimestamp(value, now, maxAgeMs) {
|
|
|
114
114
|
return now - n >= 0 && now - n <= maxAgeMs;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function isSessionFileFresh(updatedAt, filePath, now, maxAgeMs) {
|
|
118
|
+
if (updatedAt != null && updatedAt !== '') {
|
|
119
|
+
return isFreshTimestamp(updatedAt, now, maxAgeMs);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return isFreshTimestamp(statSync(filePath).mtimeMs, now, maxAgeMs);
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function emptyAmpSessionHint(filePath, fileFresh, updatedAt) {
|
|
129
|
+
return {
|
|
130
|
+
threadId: '',
|
|
131
|
+
source: '',
|
|
132
|
+
lastThreadId: '',
|
|
133
|
+
filePath: filePath || '',
|
|
134
|
+
fileFresh: Boolean(fileFresh),
|
|
135
|
+
updatedAt: updatedAt == null ? '' : updatedAt,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
117
139
|
export function readAmpSessionHint(options = {}) {
|
|
118
140
|
const env = options.env || {};
|
|
119
141
|
const homedir = options.homedir || env.HOME;
|
|
120
142
|
const now = options.now != null ? Number(options.now) : Date.now();
|
|
121
143
|
const maxAgeMs = options.maxAgeMs != null ? Number(options.maxAgeMs) : AMP_TTY_MAX_AGE_MS;
|
|
122
144
|
const filePath = join(ampDataRoot(env, homedir), 'session.json');
|
|
123
|
-
if (!existsSync(filePath)) return
|
|
145
|
+
if (!existsSync(filePath)) return emptyAmpSessionHint(filePath, false, '');
|
|
124
146
|
let data;
|
|
125
147
|
try {
|
|
126
148
|
data = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
127
149
|
} catch {
|
|
128
|
-
return
|
|
150
|
+
return emptyAmpSessionHint(filePath, isSessionFileFresh('', filePath, now, maxAgeMs), '');
|
|
151
|
+
}
|
|
152
|
+
if (!data || typeof data !== 'object') {
|
|
153
|
+
return emptyAmpSessionHint(filePath, isSessionFileFresh('', filePath, now, maxAgeMs), '');
|
|
129
154
|
}
|
|
130
|
-
if (!data || typeof data !== 'object') return { threadId: '', source: '', lastThreadId: '' };
|
|
131
155
|
const lastThreadId = trim(data.lastThreadId);
|
|
156
|
+
const updatedAt = data.updatedAt;
|
|
157
|
+
const fileFresh = isSessionFileFresh(updatedAt, filePath, now, maxAgeMs);
|
|
132
158
|
const rawTty = options.ttyKey != null ? options.ttyKey : currentTtyKey(env, options.readlink);
|
|
133
159
|
const ttyKey = isUsableTtyPath(rawTty) ? (String(rawTty).startsWith('tty:') ? rawTty : `tty:${rawTty}`) : '';
|
|
134
160
|
const byTty = data.lastThreadByTerminal && ttyKey ? data.lastThreadByTerminal[ttyKey] : null;
|
|
135
161
|
if (byTty && trim(byTty.lastThreadId) && isFreshTimestamp(byTty.updatedAt, now, maxAgeMs)) {
|
|
136
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
threadId: trim(byTty.lastThreadId),
|
|
164
|
+
source: 'amp-session-tty',
|
|
165
|
+
lastThreadId,
|
|
166
|
+
filePath,
|
|
167
|
+
fileFresh,
|
|
168
|
+
updatedAt,
|
|
169
|
+
};
|
|
137
170
|
}
|
|
138
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
threadId: '',
|
|
173
|
+
source: '',
|
|
174
|
+
lastThreadId,
|
|
175
|
+
filePath,
|
|
176
|
+
fileFresh,
|
|
177
|
+
updatedAt,
|
|
178
|
+
};
|
|
139
179
|
}
|
|
140
180
|
|
|
141
181
|
export function detectSessionClient(options = {}) {
|
|
@@ -168,6 +208,15 @@ export function detectSessionClient(options = {}) {
|
|
|
168
208
|
if (hint.threadId) {
|
|
169
209
|
return { platform: 'amp', threadId: hint.threadId, source: hint.source };
|
|
170
210
|
}
|
|
211
|
+
if (hint.lastThreadId && hint.fileFresh) {
|
|
212
|
+
return { platform: 'amp', threadId: hint.lastThreadId, source: 'amp-session-last' };
|
|
213
|
+
}
|
|
214
|
+
if (hint.fileFresh) {
|
|
215
|
+
const listed = listRecentAmpThreadIds(options);
|
|
216
|
+
if (listed[0]) {
|
|
217
|
+
return { platform: 'amp', threadId: listed[0], source: 'amp-session-list' };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
171
220
|
|
|
172
221
|
return { platform: null, threadId: null, source: 'none' };
|
|
173
222
|
}
|
package/bin/spend-collect.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
-
import { join, basename } from 'path';
|
|
2
|
+
import { join, basename, sep } from 'path';
|
|
3
3
|
import { homedir as osHomedir } from 'os';
|
|
4
4
|
import { execFileSync } from 'child_process';
|
|
5
5
|
import { listRecentAmpThreadIds } from './session-client.js';
|
|
6
6
|
import { formatUtcIso, parseFlexibleIso } from './metrics-time.js';
|
|
7
7
|
import { describeCursorCostEstimate } from './cursor-cost-estimate.js';
|
|
8
|
+
import { describeClaudeCostEstimate } from './claude-cost-estimate.js';
|
|
8
9
|
import { ampAgentMode, matchAmpUsageModel, parseAmpUsageDetails } from './amp-usage.js';
|
|
9
10
|
|
|
10
11
|
const PLATFORMS = ['cursor', 'claude', 'amp'];
|
|
@@ -216,17 +217,31 @@ function collectClaude({ cwd, windowStart, windowEnd, existing, env, homedir, no
|
|
|
216
217
|
notes.push('claude: project folder missing');
|
|
217
218
|
return sources;
|
|
218
219
|
}
|
|
219
|
-
let
|
|
220
|
+
let entries;
|
|
220
221
|
try {
|
|
221
|
-
|
|
222
|
+
entries = readdirSync(projectDir, { withFileTypes: true });
|
|
222
223
|
} catch {
|
|
223
224
|
notes.push('claude: cannot read project folder');
|
|
224
225
|
return sources;
|
|
225
226
|
}
|
|
227
|
+
const files = entries
|
|
228
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
|
|
229
|
+
.map((entry) => join(projectDir, entry.name));
|
|
230
|
+
for (const entry of entries) {
|
|
231
|
+
if (!entry.isDirectory()) continue;
|
|
232
|
+
const subagentsDir = join(projectDir, entry.name, 'subagents');
|
|
233
|
+
if (!existsSync(subagentsDir)) continue;
|
|
234
|
+
try {
|
|
235
|
+
for (const name of readdirSync(subagentsDir)) {
|
|
236
|
+
if (name.endsWith('.jsonl')) files.push(join(subagentsDir, name));
|
|
237
|
+
}
|
|
238
|
+
} catch {}
|
|
239
|
+
}
|
|
240
|
+
const bestById = new Map();
|
|
226
241
|
for (const file of files) {
|
|
227
242
|
let text;
|
|
228
243
|
try {
|
|
229
|
-
text = readFileSync(
|
|
244
|
+
text = readFileSync(file, 'utf-8');
|
|
230
245
|
} catch {
|
|
231
246
|
continue;
|
|
232
247
|
}
|
|
@@ -245,9 +260,18 @@ function collectClaude({ cwd, windowStart, windowEnd, existing, env, homedir, no
|
|
|
245
260
|
const id = message.id;
|
|
246
261
|
if (id == null || id === '') continue;
|
|
247
262
|
if (existing.has(String(id))) continue;
|
|
248
|
-
if (row.cwd !== cwd) continue;
|
|
263
|
+
if (row.cwd !== cwd && !String(row.cwd || '').startsWith(`${cwd}${sep}`)) continue;
|
|
249
264
|
if (!inWindow(row.timestamp, windowStart, windowEnd)) continue;
|
|
250
|
-
|
|
265
|
+
const cacheReadTokens = numOrNull(usage.cache_read_input_tokens ?? usage.cacheReadInputTokens);
|
|
266
|
+
const cacheCreationTokens = numOrNull(usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens);
|
|
267
|
+
const described = describeClaudeCostEstimate({
|
|
268
|
+
model: message.model,
|
|
269
|
+
inputTokens: usage.input_tokens ?? usage.inputTokens,
|
|
270
|
+
cacheReadTokens,
|
|
271
|
+
cacheCreationTokens,
|
|
272
|
+
outputTokens: usage.output_tokens ?? usage.outputTokens,
|
|
273
|
+
});
|
|
274
|
+
const record = sourceRecord({
|
|
251
275
|
id,
|
|
252
276
|
platform: 'claude',
|
|
253
277
|
model: message.model,
|
|
@@ -256,9 +280,21 @@ function collectClaude({ cwd, windowStart, windowEnd, existing, env, homedir, no
|
|
|
256
280
|
costUsd: claudeCostUsd(row, usage),
|
|
257
281
|
ampCredits: null,
|
|
258
282
|
at: row.timestamp,
|
|
259
|
-
|
|
283
|
+
cacheReadTokens,
|
|
284
|
+
costUsdEstimated: described?.usd ?? null,
|
|
285
|
+
costSource: described?.costSource ?? null,
|
|
286
|
+
});
|
|
287
|
+
const previous = bestById.get(String(id));
|
|
288
|
+
if (!previous || (record.totalTokens ?? 0) > (previous.totalTokens ?? 0)) {
|
|
289
|
+
bestById.set(String(id), record);
|
|
290
|
+
} else if ((record.totalTokens ?? 0) === (previous.totalTokens ?? 0)) {
|
|
291
|
+
const at = parseTime(record.at);
|
|
292
|
+
const previousAt = parseTime(previous.at);
|
|
293
|
+
if (Number.isFinite(at) && (!Number.isFinite(previousAt) || at < previousAt)) previous.at = record.at;
|
|
294
|
+
}
|
|
260
295
|
}
|
|
261
296
|
}
|
|
297
|
+
sources.push(...bestById.values());
|
|
262
298
|
return sources;
|
|
263
299
|
}
|
|
264
300
|
|
|
@@ -409,10 +445,14 @@ export function sourcesFromAmpThread(thread, ctx, fileName = '', via = null) {
|
|
|
409
445
|
return sources;
|
|
410
446
|
}
|
|
411
447
|
|
|
412
|
-
function collectAmp({ cwd, windowStart, windowEnd, existing, env, homedir, notes }) {
|
|
448
|
+
function collectAmp({ cwd, windowStart, windowEnd, existing, env, homedir, notes, ampThreadId, collectAll }) {
|
|
413
449
|
const root = ampRoot(env, homedir);
|
|
414
450
|
const threadsDir = join(root, 'threads');
|
|
415
451
|
const sources = [];
|
|
452
|
+
if (!ampThreadId && collectAll !== true) {
|
|
453
|
+
notes.push('amp: skipped local threads without thread id');
|
|
454
|
+
return sources;
|
|
455
|
+
}
|
|
416
456
|
if (!existsSync(threadsDir)) {
|
|
417
457
|
notes.push('amp: threads folder missing');
|
|
418
458
|
return sources;
|
|
@@ -432,6 +472,8 @@ function collectAmp({ cwd, windowStart, windowEnd, existing, env, homedir, notes
|
|
|
432
472
|
} catch {
|
|
433
473
|
continue;
|
|
434
474
|
}
|
|
475
|
+
const threadKey = thread && thread.id ? String(thread.id) : basename(file, '.json');
|
|
476
|
+
if (ampThreadId && threadKey !== String(ampThreadId)) continue;
|
|
435
477
|
sources.push(...sourcesFromAmpThread(thread, ctx, file));
|
|
436
478
|
}
|
|
437
479
|
return sources;
|
|
@@ -504,8 +546,10 @@ function collectAmpCli(ctx) {
|
|
|
504
546
|
if (id && !ids.includes(id)) ids.push(id);
|
|
505
547
|
};
|
|
506
548
|
push(ampThreadId);
|
|
507
|
-
|
|
508
|
-
|
|
549
|
+
if (ctx.listRecentAmpThreads === true) {
|
|
550
|
+
push(ampCurrentThreadId(env));
|
|
551
|
+
}
|
|
552
|
+
if (!ids.length && ctx.listRecentAmpThreads === true) {
|
|
509
553
|
for (const id of listRecentAmpThreadIds(ctx)) push(id);
|
|
510
554
|
}
|
|
511
555
|
const sources = [];
|
|
@@ -527,6 +571,10 @@ function collectAmpCli(ctx) {
|
|
|
527
571
|
...row,
|
|
528
572
|
model: matchAmpUsageModel(row.model, sourceModels),
|
|
529
573
|
}));
|
|
574
|
+
const alreadyBilled = ctx.existingThreadIds.has(id) && ctx.rebillThreadId !== id;
|
|
575
|
+
if (alreadyBilled) {
|
|
576
|
+
for (const row of usageModels) row.costUsd = null;
|
|
577
|
+
}
|
|
530
578
|
if (usage && usage.costUsd != null) {
|
|
531
579
|
for (const src of extracted) {
|
|
532
580
|
src.costSource = 'amp-usage';
|
|
@@ -535,7 +583,7 @@ function collectAmpCli(ctx) {
|
|
|
535
583
|
threads.push({
|
|
536
584
|
id,
|
|
537
585
|
agentMode,
|
|
538
|
-
costUsd: usage ? numOrNull(usage.costUsd) : null,
|
|
586
|
+
costUsd: usage && !alreadyBilled ? numOrNull(usage.costUsd) : null,
|
|
539
587
|
inputTokens: usage ? numOrNull(usage.inputTokens) : null,
|
|
540
588
|
outputTokens: usage ? numOrNull(usage.outputTokens) : null,
|
|
541
589
|
totalTokens: usage ? numOrNull(usage.totalTokens) : null,
|
|
@@ -706,7 +754,6 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, existingSources,
|
|
|
706
754
|
if (!row || typeof row !== 'object') continue;
|
|
707
755
|
const id = row.id == null || row.id === '' ? null : String(row.id);
|
|
708
756
|
if (!id) continue;
|
|
709
|
-
if (existing.has(id)) continue;
|
|
710
757
|
if (filterId) {
|
|
711
758
|
const rowConversationId = row.conversationId == null || row.conversationId === ''
|
|
712
759
|
? ''
|
|
@@ -807,6 +854,9 @@ export function collectSpend(options = {}) {
|
|
|
807
854
|
? [...options.existingSourceIds]
|
|
808
855
|
: [],
|
|
809
856
|
);
|
|
857
|
+
const existingSourceTotals = options.existingSourceTotals && typeof options.existingSourceTotals === 'object'
|
|
858
|
+
? options.existingSourceTotals
|
|
859
|
+
: {};
|
|
810
860
|
const windowStart = options.windowStart;
|
|
811
861
|
const windowEnd = options.windowEnd;
|
|
812
862
|
const notes = [];
|
|
@@ -821,16 +871,21 @@ export function collectSpend(options = {}) {
|
|
|
821
871
|
windowEnd,
|
|
822
872
|
existing,
|
|
823
873
|
existingSources,
|
|
874
|
+
existingSourceTotals,
|
|
824
875
|
env,
|
|
825
876
|
homedir,
|
|
826
877
|
notes,
|
|
827
878
|
ampThreadId: options.ampThreadId,
|
|
879
|
+
listRecentAmpThreads: options.listRecentAmpThreads,
|
|
828
880
|
cursorConversationId: options.cursorConversationId,
|
|
829
881
|
exportAmpThread: options.exportAmpThread,
|
|
830
882
|
listAmpThreads: options.listAmpThreads,
|
|
831
883
|
usageAmpThread: options.usageAmpThread,
|
|
832
884
|
ampBin: options.ampBin,
|
|
833
885
|
timeoutMs: options.timeoutMs,
|
|
886
|
+
collectAll: options.collectAll === true || options.platforms == null,
|
|
887
|
+
existingThreadIds: new Set(options.existingThreadIds || []),
|
|
888
|
+
rebillThreadId: options.rebillThreadId || null,
|
|
834
889
|
};
|
|
835
890
|
let sources = [];
|
|
836
891
|
const ampThreads = [];
|
|
@@ -864,9 +919,20 @@ export function collectSpend(options = {}) {
|
|
|
864
919
|
notes.push('cursor: adapter failed');
|
|
865
920
|
}
|
|
866
921
|
}
|
|
922
|
+
sources = sources.filter((source) => {
|
|
923
|
+
if (!source || !existing.has(String(source.id))) return true;
|
|
924
|
+
if (!Object.hasOwn(existingSourceTotals, source.id)) return false;
|
|
925
|
+
return (numOrNull(source.totalTokens) ?? 0) > (numOrNull(existingSourceTotals[source.id]) ?? 0);
|
|
926
|
+
});
|
|
867
927
|
const { byPlatform, byModel } = aggregate(sources);
|
|
868
928
|
applyAmpThreadSpend(byPlatform, byModel, ampThreads);
|
|
869
|
-
|
|
929
|
+
const ids = [...new Set(sources.map((source) => String(source.id)))];
|
|
930
|
+
const totals = {};
|
|
931
|
+
for (const source of sources) {
|
|
932
|
+
const id = String(source.id);
|
|
933
|
+
totals[id] = Math.max(numOrNull(totals[id]) ?? 0, numOrNull(source.totalTokens) ?? 0);
|
|
934
|
+
}
|
|
935
|
+
return { sources, ids, totals, byPlatform, byModel, notes, ampThreads };
|
|
870
936
|
}
|
|
871
937
|
|
|
872
938
|
function applyAmpThreadSpend(byPlatform, byModel, threads) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Universal AI agent orchestration kit for Cursor, Claude Code, and Amp Code — spec-driven OpenSpec pipeline, conductor subagents, durable session handoff, factory gates and MCP setup, cloud-agent handoff, and optional local Figma PAT setup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -35,6 +35,12 @@ When ready to implement, run /opsx:apply
|
|
|
35
35
|
|
|
36
36
|
Each task must be self-contained for a blind implementer — executable without reading design.md. `Files:` paths must exist unless prefixed with `new file:`. Lint: `npx agent-orchestrator-kit gate-check --tasks <name>` (mode via `pipeline.task_contract: warn|strict|off`).
|
|
37
37
|
|
|
38
|
+
On re-propose after `review.md` Verdict REQUEST CHANGES, the conductor MUST pass `review.md` (path + verdict + Required Before Apply list) in the `spec-architect` spawn prompt and verify the report addresses every item; the parent MUST NOT itself edit proposal/design/specs/tasks. Exception: the structure-only propose trigger is the exact line
|
|
39
|
+
|
|
40
|
+
**Source:** gate-check
|
|
41
|
+
|
|
42
|
+
plus the absence of `## Checklist`; then fix only those gate-check errors.
|
|
43
|
+
|
|
38
44
|
**Steps**
|
|
39
45
|
|
|
40
46
|
1. **If no input provided, ask what they want to build**
|
|
@@ -38,11 +38,15 @@ npx agent-orchestrator-kit gate-check --review <name>
|
|
|
38
38
|
|
|
39
39
|
The script runs `openspec validate --strict --type change`, the task-contract lint (Files/Do/Done-when), the `Non-goals` / `Acceptance criteria` proposal sections check, and non-empty ADDED/MODIFIED/REMOVED delta-spec sections check. Add `--json` for a `{pass, errors[]}` report.
|
|
40
40
|
|
|
41
|
-
**If Tier 1 fails (exit ≠ 0):** do NOT spawn `spec-reviewer` and do NOT read the artifacts. Write `openspec/changes/<name>/review.md` with `Verdict: REQUEST CHANGES` listing the gate-check errors
|
|
41
|
+
**If Tier 1 fails (exit ≠ 0):** do NOT spawn `spec-reviewer` and do NOT read the artifacts. Write `openspec/changes/<name>/review.md` with `Verdict: REQUEST CHANGES` listing the gate-check errors. The T1 file MUST include this exact line:
|
|
42
|
+
|
|
43
|
+
**Source:** gate-check
|
|
44
|
+
|
|
45
|
+
The T1 `review.md` has no `## Checklist` section. This parent-written T1 `review.md` is an **accepted exception** to pipeline-subagents «parent MUST NOT write the verdict». Output the Request Changes verdict in chat. After an accepted RC one line MUST contain `Verdict: REQUEST CHANGES` and `/opsx:propose`. Go straight to Session Exit.
|
|
42
46
|
|
|
43
47
|
### 3. Tier 2 — spawn the specialist
|
|
44
48
|
|
|
45
|
-
Only after Tier 1 passes: spawn `spec-reviewer` with the complete change paths, project constraints, and the shortened checklist below. Require `## Subagent report: spec-reviewer`. Do not perform the review in the parent session.
|
|
49
|
+
Only after Tier 1 passes: spawn `spec-reviewer` with the complete change paths, project constraints, and the shortened checklist below. The parent MUST paste the full Tier 2 checklist from this file into the `spec-reviewer` prompt, including the Vue 3 items when `project.stack: vue3`. Before spawn, the parent MUST record whether `openspec/changes/<name>/review.md` already existed, and pass that fact plus the path in the spawn prompt. Require `## Subagent report: spec-reviewer`. Do not perform the review in the parent session.
|
|
46
50
|
|
|
47
51
|
### 4. Review checklist (Tier 2 — LLM-only)
|
|
48
52
|
|
|
@@ -68,6 +72,10 @@ Do NOT re-check what Tier 1 already covered (strict validation, contract field p
|
|
|
68
72
|
- [ ] Tasks reference concrete component/store paths under `src/`
|
|
69
73
|
- [ ] No scope creep into unrelated UI refactors
|
|
70
74
|
|
|
75
|
+
MUST NOT stop at the first blocking finding. Finish the full LLM checklist and a complete scan of proposal.md, design.md, tasks.md, all delta specs, and referenced main specs/repo paths before writing the verdict. One ✗ still means REQUEST CHANGES, but list every blocking issue of that pass.
|
|
76
|
+
|
|
77
|
+
Re-review MUST scan the same defect class — LLM-only only (another task whose `Do:` is not executable without design.md; another design behaviour with no delta requirement; another proposal↔tasks drift; another referenced heading/path that does not exist). Tier 1 classes NEVER enter this rescan. MUST NOT emit a one-item RC that names only the first leftover.
|
|
78
|
+
|
|
71
79
|
### 5. Write and report the verdict
|
|
72
80
|
|
|
73
81
|
#### If all ✓ (or only minor notes):
|
|
@@ -110,12 +118,16 @@ Create or update `openspec/changes/<name>/review.md`:
|
|
|
110
118
|
|
|
111
119
|
On **APPROVE**, `spec-reviewer` also writes `openspec/changes/<name>/apply-notes.md` (≤ 20 lines): critical constraints, pitfalls, what NOT to touch, verification commands. It is the distilled input for `/opsx:apply` and the **second allowed file** next to `review.md`.
|
|
112
120
|
|
|
113
|
-
For **REQUEST CHANGES**, write only `review.md` with `Verdict: REQUEST CHANGES` and the
|
|
121
|
+
For **REQUEST CHANGES**, write only `review.md` with `Verdict: REQUEST CHANGES` and the required sections: Checklist (each T2 item ✓/✗), Findings (Blocker / Major / Minor; empty buckets allowed), Required Before Apply (blocking only), Previous findings. Cosmetics stay out of Required Before Apply.
|
|
122
|
+
|
|
123
|
+
The `Previous findings` heading is ALWAYS present after any Tier 2 pass. If no prior `review.md` existed, the body is the literal line `none — first review cycle`. If a prior file existed, each prior Required Before Apply item → `resolved` | `unresolved` plus one-line evidence.
|
|
114
124
|
|
|
115
125
|
`review.md` (always) and `apply-notes.md` (on APPROVE) are the **only files** you may write during review (not `src/`, not `tasks.md` checkboxes).
|
|
116
126
|
|
|
117
127
|
The conductor verifies the subagent's `Status: done`, checks that `review.md` exists with the reported verdict (and `apply-notes.md` on APPROVE), and relays the result without editing them.
|
|
118
128
|
|
|
129
|
+
After Tier 2, the conductor MUST reject an RC `review.md` that lacks those headings or has an empty Checklist and MUST NOT rewrite the file; then re-spawn `spec-reviewer` once with the rejection reason and required headings; if the second file is still non-conforming, close with `## Blocked` naming the missing headings and next command `/opsx:review <name>` (a rejected file is not an accepted verdict). NEXT-AFTER-RC applies only to an accepted (schema-conforming) RC.
|
|
130
|
+
|
|
119
131
|
#### If any ✗:
|
|
120
132
|
|
|
121
133
|
```
|
|
@@ -123,25 +135,37 @@ The conductor verifies the subagent's `Status: done`, checks that `review.md` ex
|
|
|
123
135
|
|
|
124
136
|
**Change:** <name>
|
|
125
137
|
|
|
126
|
-
###
|
|
138
|
+
### Checklist
|
|
139
|
+
- proposal ↔ design ↔ tasks: ✓ or ✗
|
|
140
|
+
- Delta specs cover design: ✓ or ✗
|
|
141
|
+
- No conflicts with main specs: ✓ or ✗
|
|
142
|
+
- No scope creep vs Non-goals: ✓ or ✗
|
|
143
|
+
- Task self-sufficiency: ✓ or ✗
|
|
144
|
+
- Vue 3 items (when `project.stack: vue3`): ✓ or ✗ each
|
|
145
|
+
|
|
146
|
+
### Findings
|
|
147
|
+
|
|
148
|
+
#### Blocker
|
|
149
|
+
- <or empty>
|
|
127
150
|
|
|
128
|
-
####
|
|
129
|
-
-
|
|
151
|
+
#### Major
|
|
152
|
+
- <or empty>
|
|
130
153
|
|
|
131
|
-
####
|
|
132
|
-
-
|
|
154
|
+
#### Minor
|
|
155
|
+
- <or empty>
|
|
133
156
|
|
|
134
157
|
### Required Before Apply
|
|
135
|
-
<
|
|
158
|
+
- <blocking only>
|
|
136
159
|
|
|
137
|
-
|
|
160
|
+
### Previous findings
|
|
161
|
+
none — first review cycle
|
|
138
162
|
```
|
|
139
163
|
|
|
140
164
|
---
|
|
141
165
|
|
|
142
166
|
## Session Exit (HARD STOP)
|
|
143
167
|
|
|
144
|
-
Close via the canonical Session Exit protocol in `.agents/rules/session-handoff.mdc`.
|
|
168
|
+
Close via the canonical Session Exit protocol in `.agents/rules/session-handoff.mdc`. After an accepted REQUEST CHANGES one line contains `Verdict: REQUEST CHANGES` and `/opsx:propose`; after APPROVE `/opsx:apply <name>`. NEXT-AFTER-RC applies only to an accepted (schema-conforming) RC. Do not start the next phase in this chat.
|
|
145
169
|
|
|
146
170
|
## Guardrails
|
|
147
171
|
|
|
@@ -20,13 +20,13 @@ Agents (local or cloud) write session artifacts only to git-tracked paths — ne
|
|
|
20
20
|
|
|
21
21
|
## Session Exit (order)
|
|
22
22
|
1. The parent writes `openspec/changes/<name>/handoff.md` itself: Closed role, Change, Done, Decisions, Blocked, Next command, Next role, Attach, Subagents to spawn, Constraints, Runtime, Metrics.
|
|
23
|
-
2. Fill `## Metrics` before
|
|
23
|
+
2. Fill `## Metrics` before persist: `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`. `platform` and product-id `model` are required and never `unknown`; unknown numbers use `unknown`, never invented `0`. Do not set self-report when all numbers are unknown. Put decisions in `## Decisions`; only the CLI writes `decisions.md`.
|
|
24
24
|
3. `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` — require exit 0 (appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md`, upserts absolute-path Memory JSON, records the session into `openspec/changes/<name>/metrics.json`, prints the expanded prompt on stdout). `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`, `accounts/fireworks/models/glm-5p2`) — NEVER pass a Closed role, a subagent name, or an Amp **mode** (`low`, `medium`, `high`, `ultra`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. Persist collects spend for the client locked at restore (Amp: `amp threads export` + local threads; Cursor: hook file; Claude: JSONL). `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps; they do not rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` runs all three adapters, not only the locked client. The same `npx agent-orchestrator-kit handoff <name>` works in Cursor, Claude Code, and Amp. `decisions.md` is the git canon of change decisions; Memory `Decision:*` is a file→Memory mirror only. Cloud sessions pass `--runtime cloud` (or set `AOK_RUNTIME=cloud` / `AOK_AGENT_ID` in the cloud-agent environment).
|
|
25
25
|
4. Spawn `session-handoff` in persist mode ONLY if step 3 failed (Amp: isolated `subagent-session-handoff`). Fallback, never routine.
|
|
26
26
|
5. Memory MCP is an optional mirror: if tools are available, update `Change:<name>`, `Handoff:<name>`, `Decision:*` in one call; unavailability never blocks closing.
|
|
27
27
|
6. Paste CLI stdout as one fenced block. First line `/opsx:…`. Body uses `project.agent_language`. Self-contained (Done/Decisions/Blocked/spawn/HARD STOP). No banner.
|
|
28
28
|
7. If runtime is cloud: after persist, `git add openspec/changes/<name>/` → `git commit` → `git push` → `npx agent-orchestrator-kit handoff <name> --cloud-check` (exit 0 required). Closing without this is an incomplete handoff. Persist prints these steps on stderr; the CLI never runs `git commit` / `git push`.
|
|
29
|
-
8. Stop. Next role = new chat.
|
|
29
|
+
8. Stop. Next role and any out-of-OpenSpec hotfix = new chat. Never run full persist twice; regenerate the prompt only with `handoff <name> --no-metrics`.
|
|
30
30
|
|
|
31
31
|
## Archive exception
|
|
32
32
|
`npx agent-orchestrator-kit archive <name>` writes the final `handoff.md` (`next_command: none`) in the archive folder and upserts memory itself. After a successful archive no fenced next-prompt is required — the pipeline is complete.
|
|
@@ -109,7 +109,7 @@ Before apply, check `.agents/orchestrator.yaml`:
|
|
|
109
109
|
- `require_spec_review: true` → apply MUST find `review.md` with `Verdict: APPROVE` or Approve in session
|
|
110
110
|
- `require_spec_review: false` → apply allowed directly (mvp / quick mode)
|
|
111
111
|
|
|
112
|
-
If Request Changes — fix
|
|
112
|
+
If Request Changes — run `/opsx:propose <name>` to fix the punch list, then a new `/opsx:review`.
|
|
113
113
|
|
|
114
114
|
This is no longer only a chat convention: `npx agent-orchestrator-kit gate-check` runs in CI (both `agent-verify.yml` fragments) and fails the pipeline if `src/` changed without an approved `review.md` — a forgotten or skipped review is caught at merge time, not just at apply time. When `require_design_brief: true`, the same command also requires `design-brief.md` (or `Design: none` in `proposal.md`).
|
|
115
115
|
|
|
@@ -146,11 +146,11 @@ Archive is one deterministic CLI call — `npx agent-orchestrator-kit archive <n
|
|
|
146
146
|
|
|
147
147
|
**End of each session (HARD STOP — you are NOT done):**
|
|
148
148
|
1. Write `openspec/changes/<name>/handoff.md` in the parent using the template below, including `## Metrics`.
|
|
149
|
-
2. Fill `## Metrics`
|
|
149
|
+
2. Fill `## Metrics` before persist. `platform` and product-id `model` are required and never `unknown`; unknown numbers use `unknown`, never invented `0`. Put decisions in `## Decisions`; only the CLI writes `decisions.md`.
|
|
150
150
|
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps or rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. The CLI appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (the git canon), upserts Memory JSON with an absolute path (`Decision:*` is a file→Memory mirror only), and prints the expanded self-contained prompt on stdout. Spawn `session-handoff` in persist mode ONLY if this CLI step failed.
|
|
151
151
|
4. If Memory MCP tools are available, mirror `Change:<name>`, `Handoff:<name>`, and new `Decision:<topic>` entities in one call — optional; its absence never blocks closing.
|
|
152
152
|
5. Paste the CLI stdout as one fenced next-session prompt. First line is `/opsx:<next> <name>`; body uses `project.agent_language`; keep Done/Decisions/Blocked/spawn/HARD STOP complete. No banner. Do not emit a thin “read Memory” stub.
|
|
153
|
-
6. Do not start the next phase in this chat. If apply, include build/lint status in
|
|
153
|
+
6. Do not start the next phase in this chat. Never run full persist twice; regenerate the prompt only with `handoff <name> --no-metrics`. Any out-of-OpenSpec hotfix after persist requires a new chat. If apply, include build/lint status in Done.
|
|
154
154
|
|
|
155
155
|
`handoff.md` template:
|
|
156
156
|
|
|
@@ -257,12 +257,13 @@ Before declaring a session closed, the parent MUST, in order: (1) write `openspe
|
|
|
257
257
|
| No archive after merge | Next propose has stale domain specs |
|
|
258
258
|
| Strong model on lint fixes | 5–10x cost with no quality gain |
|
|
259
259
|
| Skip Memory MCP / skip `handoff` CLI | Next thread has no context; Amp looks like it “ignored the rules” |
|
|
260
|
+
| one-finding review loop | fragments defects across many propose/review sessions |
|
|
260
261
|
|
|
261
262
|
## Metrics (health check per change)
|
|
262
263
|
|
|
263
264
|
- Sessions: 4–8 (not 1 marathon, not 20 micro-sessions)
|
|
264
265
|
- Apply iterations to PR: ≤ 2
|
|
265
|
-
- Spec review loops: ≤ 1
|
|
266
|
+
- Spec review discovery loops: ≤ 2 (optional Tier 1 structural RC plus one semantic Tier 2 RC; a confirmation APPROVE after an exhaustive propose does not count as a discovery loop)
|
|
266
267
|
- Tasks rework: ≤ 10%
|
|
267
268
|
|
|
268
269
|
If apply iterations > 2 → problem is in Architect or Reviewer, not Implementer.
|
|
@@ -35,6 +35,12 @@ When ready to implement, run /opsx:apply
|
|
|
35
35
|
|
|
36
36
|
Each task must be self-contained for a blind implementer — executable without reading design.md. `Files:` paths must exist unless prefixed with `new file:`. Lint: `npx agent-orchestrator-kit gate-check --tasks <name>` (mode via `pipeline.task_contract: warn|strict|off`).
|
|
37
37
|
|
|
38
|
+
On re-propose after `review.md` Verdict REQUEST CHANGES, the conductor MUST pass `review.md` (path + verdict + Required Before Apply list) in the `spec-architect` spawn prompt and verify the report addresses every item; the parent MUST NOT itself edit proposal/design/specs/tasks. Exception: the structure-only propose trigger is the exact line
|
|
39
|
+
|
|
40
|
+
**Source:** gate-check
|
|
41
|
+
|
|
42
|
+
plus the absence of `## Checklist`; then fix only those gate-check errors.
|
|
43
|
+
|
|
38
44
|
**Steps**
|
|
39
45
|
|
|
40
46
|
1. **If no clear input provided, ask what they want to build**
|
|
@@ -15,8 +15,9 @@ On every invocation:
|
|
|
15
15
|
4. Map what you find to the correct next command:
|
|
16
16
|
- No `proposal.md` yet → `/opsx:propose <name>`
|
|
17
17
|
- `require_design_brief: true`, UI-touching change, no `design-brief.md`, no `Design: none` in `proposal.md` → `/opsx:design <name>`
|
|
18
|
-
- `proposal.md` exists but no `review.md`
|
|
19
|
-
- `review.md`
|
|
18
|
+
- `proposal.md` exists but no `review.md` → `/opsx:review <name>` (must run in a separate read-only session)
|
|
19
|
+
- `review.md` contains `Verdict: REQUEST CHANGES` → `/opsx:propose <name>`
|
|
20
|
+
- `review.md` has `Verdict: APPROVE` but `tasks.md` has unchecked `- [ ]` items → `/opsx:apply <name>`
|
|
20
21
|
- All tasks `[x]` and review approved → ready to archive, suggest `/opsx:archive <name>` (or note that GitLab/GitHub CI auto-archives after merge if `archive_after_merge: true`)
|
|
21
22
|
5. If a CI gate (`gate-check`, `verify-openspec-pr`) is failing, reproduce the check locally (`npx agent-orchestrator-kit gate-check <name>`, `npm run verify:openspec:pr`) and quote the exact failing reason from its output — don't guess.
|
|
22
23
|
6. If `pipeline.max_active_changes` is exceeded, say so explicitly and name which changes are over the limit.
|
|
@@ -22,7 +22,7 @@ Use when the parent's restore failed (CLI restore and handoff.md both unavailabl
|
|
|
22
22
|
Use when the parent's persist failed (`npx agent-orchestrator-kit handoff <name>` did not exit 0). A session is not closed until persist succeeds.
|
|
23
23
|
|
|
24
24
|
1. Write or update `openspec/changes/<name>/handoff.md` with every required section: Closed role, Change, Done, Decisions, Blocked, Next command, Next role, Attach, Subagents to spawn, Constraints, Runtime, Metrics.
|
|
25
|
-
2. Fill `## Metrics`
|
|
25
|
+
2. Fill `## Metrics` before persist. `platform` and product-id `model` are required and never `unknown`; unknown numbers use `unknown`, never invented `0`. Put decisions in `## Decisions`; only the CLI writes `decisions.md`.
|
|
26
26
|
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. This appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (git canon), upserts `.cursor/memory.json` using an absolute path (`Decision:*` mirrors that file, never the reverse), and prints the expanded next-session prompt on stdout. Cloud sessions pass `--runtime cloud` (or `AOK_RUNTIME` / `AOK_AGENT_ID`).
|
|
27
27
|
4. If Memory MCP tools are available, also create/update `Change:<name>`, `Handoff:<name>`, and each `Decision:<topic>` to match `decisions.md`. MCP failure is not a blocker after the CLI succeeds.
|
|
28
28
|
5. Put the CLI stdout prompt (first line `/opsx:…`) into **Next prompt** unchanged. Do not shorten it. Do not add a banner.
|
|
@@ -33,6 +33,7 @@ Use when the parent's persist failed (`npx agent-orchestrator-kit handoff <name>
|
|
|
33
33
|
- Write session artifacts only to git-tracked paths (never `/tmp`, never gitignored caches).
|
|
34
34
|
- Do NOT edit `src/`, tests, main specs, `tasks.md` checkboxes, or phase artifacts (`proposal.md`, `review.md`, `design-brief.md`) except `handoff.md`.
|
|
35
35
|
- Do NOT start the next OpenSpec phase.
|
|
36
|
+
- Never run full persist twice. Regenerate a prompt only with `handoff <name> --no-metrics`; after persist, move any next-role or out-of-OpenSpec work to a new chat.
|
|
36
37
|
- Do NOT return a thin prompt. The next thread must be able to run if Memory MCP is ignored.
|
|
37
38
|
- Stop as blocked when the change name or next command cannot be resolved.
|
|
38
39
|
|
|
@@ -11,7 +11,12 @@ Workflow:
|
|
|
11
11
|
2. Create or update `proposal.md`, `design.md`, `specs/<capability>/spec.md`, and `tasks.md` using the repository's OpenSpec schema and conventions.
|
|
12
12
|
3. Keep requirements testable: each requirement uses SHALL/MUST language and includes concrete scenarios.
|
|
13
13
|
4. Make tasks ordered, independently verifiable, and traceable to the design and delta specs. Every task MUST follow the task contract: indented `Files:` (existing paths, or `new file:` prefix for new ones), `Do:` (concrete change, no vague wording like "as needed" / "if necessary" / "as appropriate"), and `Done-when:` (verifiable condition or command). Each task must be self-contained for a blind implementer without reading design.md.
|
|
14
|
-
5.
|
|
14
|
+
5. On re-propose after REQUEST CHANGES, the architect MUST read `review.md`, fix every Required Before Apply item, and re-scan the same defect class in proposal.md, design.md, tasks.md, and all delta specs (LLM-only classes only: another task whose `Do:` is not executable without design.md; another design behaviour with no delta requirement; another proposal↔tasks drift; another referenced heading/path that does not exist); do not stop after the listed items; Tier 1 classes NEVER enter this rescan. Exception: the structure-only propose trigger is the exact line
|
|
15
|
+
|
|
16
|
+
**Source:** gate-check
|
|
17
|
+
|
|
18
|
+
plus the absence of `## Checklist`; then fix only those gate-check errors.
|
|
19
|
+
6. Report which validation command the conductor should run; do not cross into review or implementation.
|
|
15
20
|
|
|
16
21
|
Rules:
|
|
17
22
|
|
|
@@ -9,7 +9,7 @@ Workflow:
|
|
|
9
9
|
|
|
10
10
|
1. Read `.agents/orchestrator.yaml`, the complete change, review verdict, task state, and verification/merge evidence supplied by the conductor.
|
|
11
11
|
2. Refuse to archive unless required review is approved, all tasks are complete, and the configured merge/CI gate is satisfied.
|
|
12
|
-
3. Fill `## Metrics`
|
|
12
|
+
3. Fill `## Metrics` with Archiver-only numbers before archive. `platform` and product-id `model` are required and never `unknown`; unknown numbers use `unknown`, never invented `0`. Put decisions in `## Decisions`; only the CLI writes `decisions.md`. Do not copy the apply session.
|
|
13
13
|
4. Run `npx agent-orchestrator-kit archive <name>` so delta requirements are merged into main specs, the change moves to the dated archive path, and stdout prints the change-wide metrics summary (by phase / by platform / by model).
|
|
14
14
|
5. Run strict validation after the move and report the resulting archive path and modified main specs.
|
|
15
15
|
|
|
@@ -18,6 +18,7 @@ Rules:
|
|
|
18
18
|
- Do NOT edit `src/`, tests, CI, or implementation files.
|
|
19
19
|
- Do NOT add new features, redesign requirements, or repair incomplete implementation during archive.
|
|
20
20
|
- Do NOT manually discard delta requirements to make validation pass.
|
|
21
|
+
- Do not run archive/persist twice; after success, stop and move out-of-OpenSpec work to a new chat. Prompt regeneration before archive uses `handoff <name> --no-metrics`.
|
|
21
22
|
- If archive prerequisites are missing, return `blocked` with the exact unmet gate.
|
|
22
23
|
|
|
23
24
|
Return exactly this report contract:
|