@peterxiaoyang/superspec 0.1.4 → 0.1.6
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/adapters/codex/agents/architect.toml +4 -148
- package/adapters/codex/agents/code-reviewer.toml +4 -166
- package/adapters/codex/agents/critic.toml +5 -106
- package/adapters/codex/agents/executor.toml +13 -0
- package/adapters/codex/agents/test-engineer.toml +4 -154
- package/adapters/codex/agents/test-runner.toml +13 -0
- package/adapters/codex/agents/verifier.toml +4 -110
- package/adapters/codex/install-map.json +20 -0
- package/dist/src/apply_worker_chain.d.ts +57 -0
- package/dist/src/apply_worker_chain.js +1188 -0
- package/dist/src/cli.js +13 -0
- package/dist/src/cli_args.d.ts +13 -1
- package/dist/src/cli_args.js +237 -12
- package/dist/src/core.d.ts +1 -0
- package/dist/src/core.js +1 -0
- package/dist/src/evidence.js +152 -0
- package/dist/src/gates.d.ts +2 -1
- package/dist/src/gates.js +275 -21
- package/dist/src/i18n.js +4 -3
- package/dist/src/install_engine.d.ts +17 -0
- package/dist/src/install_engine.js +125 -2
- package/dist/src/packet_measure.d.ts +43 -0
- package/dist/src/packet_measure.js +417 -0
- package/dist/src/packet_render.d.ts +4 -0
- package/dist/src/packet_render.js +1623 -0
- package/dist/src/packet_schema.d.ts +56 -0
- package/dist/src/packet_schema.js +1 -0
- package/dist/src/project_init.js +7 -49
- package/dist/src/tasks.d.ts +10 -0
- package/dist/src/tasks.js +86 -0
- package/dist/src/util.d.ts +11 -3
- package/dist/src/util.js +27 -6
- package/package.json +2 -2
- package/schemas/install-manifest.schema.json +17 -0
- package/templates/workflow/prompts/architect.md +16 -109
- package/templates/workflow/prompts/code-reviewer.md +20 -134
- package/templates/workflow/prompts/critic.md +18 -75
- package/templates/workflow/prompts/executor.md +32 -0
- package/templates/workflow/prompts/test-engineer.md +16 -126
- package/templates/workflow/prompts/test-runner.md +33 -0
- package/templates/workflow/prompts/verifier.md +20 -77
- package/templates/workflow/skills/superspec-apply/SKILL.md +102 -78
- package/templates/workflow/skills/superspec-archive/SKILL.md +41 -37
- package/templates/workflow/skills/superspec-explore/SKILL.md +63 -77
- package/templates/workflow/skills/superspec-propose/SKILL.md +64 -85
- package/templates/workflow/skills/superspec-review/SKILL.md +76 -233
package/dist/src/gates.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
-
import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS,
|
|
3
|
+
import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, REVIEW_GUIDANCE_ROLES, REVIEW_EVIDENCE_REQUIRED_FIELDS, TDD_MODES, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, isObject, reason, renderList, repr, pinned_ref_key, safe_within, sha256_text, runtime, toPosix, } from "./util.js";
|
|
4
4
|
import { all_done, artifact_status_map, get_repo_root, is_done, normalize_gate, openspec_cli_probe } from "./openspec.js";
|
|
5
5
|
import { read_agent_toml_name, read_skill_frontmatter_name, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
|
|
6
6
|
import { business_invariant_ids, business_invariant_validation_reasons, automated_hard_business_invariant_ids, evidence_invariant_refs, evidence_invariant_ref_reasons, evidence_test_contract_invariant_reasons, human_confirmation_business_invariant_ids, invariant_matrix_coverage_reasons, post_implementation_business_invariant_ids, red_green_invariant_ids, test_contract_invariant_ids, } from "./invariants.js";
|
|
7
7
|
import { evidence_test_id_reasons, declared_test_evidence_reasons, parse_spec_scenarios, parse_tasks, parse_test_contract_ids, parse_test_contract_records, red_green_test_ids, splitList, tasks_structure_hash, task_alternative_verification, task_test_evidence, task_test_refs, test_contract_covers_scenario, test_contract_invariant_refs_by_test, write_scope_conflict_reasons, } from "./tasks.js";
|
|
8
|
-
import { duplicate_evidence_id_reasons, dangling_evidence_ref_reasons, final_verification_evidences, live_task_reopens, live_task_reopen_resolutions, live_pass, live_user_confirmations, pass_task_reopens, supersede_reasons, unresolved_live_task_reopens, validate_evidence_schema, verify_reference_reasons, } from "./evidence.js";
|
|
8
|
+
import { duplicate_evidence_id_reasons, dangling_evidence_ref_reasons, find_pass, final_verification_evidences, live_task_reopens, live_task_reopen_resolutions, live_pass, live_user_confirmations, pass_task_reopens, supersede_reasons, unresolved_live_task_reopens, validate_evidence_schema, verify_reference_reasons, } from "./evidence.js";
|
|
9
9
|
import { review_disclosure_reasons } from "./disclosure.js";
|
|
10
10
|
import { archive_manifest_path } from "./archive.js";
|
|
11
|
+
import { apply_worker_implementation_fingerprint, apply_worker_executor_input_ref_digest, compute_apply_worker_freshness, fingerprint_matches, pinned_artifact_ref_reasons as shared_pinned_artifact_ref_reasons, pre_edit_evidence_ref_reasons, read_pinned_artifact_json, worker_input_ref_digest, worker_test_run_reasons, } from "./apply_worker_chain.js";
|
|
11
12
|
function action_list(...items) {
|
|
12
13
|
return [...new Set(items.filter((item) => typeof item === "string" && item.length > 0))];
|
|
13
14
|
}
|
|
@@ -330,24 +331,8 @@ function default_gate_next_actions(gate) {
|
|
|
330
331
|
return [];
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
|
-
export function openspec_init_reasons(
|
|
334
|
-
|
|
335
|
-
reasons.push(...runtime.openspec_cli_capability_reasons());
|
|
336
|
-
const skillsRoot = join(repoRoot, ".codex", "skills");
|
|
337
|
-
const missing = REQUIRED_OPENSPEC_CODEX_SKILLS.filter((name) => !existsSync(join(skillsRoot, name, "SKILL.md")));
|
|
338
|
-
if (missing.length > 0) {
|
|
339
|
-
reasons.push(reason("openspec_init_missing", "OpenSpec native Codex skills are missing; run `openspec init --tools codex .` or `openspec update --force .` from the repository root", missing));
|
|
340
|
-
}
|
|
341
|
-
for (const name of REQUIRED_OPENSPEC_CODEX_SKILLS) {
|
|
342
|
-
const skillPath = join(skillsRoot, name, "SKILL.md");
|
|
343
|
-
if (!existsSync(skillPath))
|
|
344
|
-
continue;
|
|
345
|
-
const declared = read_skill_frontmatter_name(skillPath);
|
|
346
|
-
if (declared !== name) {
|
|
347
|
-
reasons.push(reason("openspec_native_surface_invalid", `OpenSpec native skill ${skillPath} has invalid front matter name ${repr(declared)}`, [skillPath]));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return reasons;
|
|
334
|
+
export function openspec_init_reasons(_repoRoot) {
|
|
335
|
+
return runtime.openspec_cli_capability_reasons();
|
|
351
336
|
}
|
|
352
337
|
// D4 (audit G-2): check-init must notice when SuperSpec's own workflow skills are missing or
|
|
353
338
|
// corrupted, otherwise a deleted .codex/skills/superspec-* surface stays invisible end to end.
|
|
@@ -1160,6 +1145,260 @@ export function check_task_edit(change, status, changeRoot, evidences, taskId) {
|
|
|
1160
1145
|
}
|
|
1161
1146
|
return allow(change, gate, { task_id: taskId });
|
|
1162
1147
|
}
|
|
1148
|
+
function pinned_artifact_ref_matches(changeRoot, refItem, role, taskId, chainId, kind = "worker_report") {
|
|
1149
|
+
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, `${role}_ref`, { kind, role, taskId, chainId }).length === 0;
|
|
1150
|
+
}
|
|
1151
|
+
function executor_worker_test_run_refs_valid(changeRoot, ev, taskId, chainId) {
|
|
1152
|
+
return worker_test_run_reasons(changeRoot, ev, taskId, chainId).length === 0;
|
|
1153
|
+
}
|
|
1154
|
+
function report_origin_matches(refItem, expected) {
|
|
1155
|
+
return isObject(refItem) && typeof expected === "string" && refItem.origin_packet_fingerprint === expected;
|
|
1156
|
+
}
|
|
1157
|
+
function report_input_refs_match(refItem, refs) {
|
|
1158
|
+
return isObject(refItem) && refItem.input_ref_digest === worker_input_ref_digest(refs);
|
|
1159
|
+
}
|
|
1160
|
+
function report_input_digest_matches(refItem, expectedDigest) {
|
|
1161
|
+
return isObject(refItem) && refItem.input_ref_digest === expectedDigest;
|
|
1162
|
+
}
|
|
1163
|
+
function chain_green_ids(ev) {
|
|
1164
|
+
if (Array.isArray(ev.green_test_run_evidence_refs)) {
|
|
1165
|
+
return ev.green_test_run_evidence_refs.map(String).filter(Boolean).sort();
|
|
1166
|
+
}
|
|
1167
|
+
const greenId = isObject(ev.green_test_run_evidence_ref)
|
|
1168
|
+
? String(ev.green_test_run_evidence_ref.evidence_id ?? "")
|
|
1169
|
+
: String(ev.green_test_run_evidence_ref ?? "");
|
|
1170
|
+
return greenId ? [greenId] : [];
|
|
1171
|
+
}
|
|
1172
|
+
function active_pre_edit_input_refs(evidences, active) {
|
|
1173
|
+
const preIds = Array.isArray(active.pre_edit_evidence_refs) ? active.pre_edit_evidence_refs.map(String) : [];
|
|
1174
|
+
return preIds.map((evidenceId) => {
|
|
1175
|
+
const ev = live_pass(evidences, { kind: "test_run" }).find((item) => String(item.evidence_id ?? "") === evidenceId);
|
|
1176
|
+
return ev
|
|
1177
|
+
? { kind: "test_run", evidence_id: evidenceId, phase: ev.phase, semantic_status: ev.semantic_status }
|
|
1178
|
+
: { kind: "test_run", evidence_id: evidenceId };
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
function closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId) {
|
|
1182
|
+
const active = live_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId })
|
|
1183
|
+
.find((item) => item.chain_state === "active" && item.apply_worker_chain_id === chainId);
|
|
1184
|
+
if (!active)
|
|
1185
|
+
return false;
|
|
1186
|
+
if (pre_edit_evidence_ref_reasons(evidences, active, taskId).length > 0)
|
|
1187
|
+
return false;
|
|
1188
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.executor_report_ref, "executor", taskId, chainId))
|
|
1189
|
+
return false;
|
|
1190
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.task_code_review_report_ref, "code-reviewer", taskId, chainId))
|
|
1191
|
+
return false;
|
|
1192
|
+
if (!pinned_artifact_ref_matches(changeRoot, ev.verifier_report_ref, "verifier", taskId, chainId))
|
|
1193
|
+
return false;
|
|
1194
|
+
if (!report_origin_matches(ev.executor_report_ref, active.executor_packet_fingerprint))
|
|
1195
|
+
return false;
|
|
1196
|
+
if (!report_input_digest_matches(ev.executor_report_ref, apply_worker_executor_input_ref_digest(evidences, active)))
|
|
1197
|
+
return false;
|
|
1198
|
+
if (!report_input_refs_match(ev.task_code_review_report_ref, [ev.executor_report_ref]))
|
|
1199
|
+
return false;
|
|
1200
|
+
if (typeof ev.observed_freshness_fingerprint !== "string" && !(isObject(ev.observed_freshness_fingerprint) && typeof ev.observed_freshness_fingerprint.fingerprint_digest === "string"))
|
|
1201
|
+
return false;
|
|
1202
|
+
const verifierReport = read_pinned_artifact_json(changeRoot, ev.verifier_report_ref);
|
|
1203
|
+
const verifierObserved = verifierReport?.observed_freshness_fingerprint ?? (isObject(ev.verifier_report_ref) ? ev.verifier_report_ref.observed_freshness_fingerprint : undefined);
|
|
1204
|
+
if (!fingerprint_matches(verifierObserved, ev.observed_freshness_fingerprint))
|
|
1205
|
+
return false;
|
|
1206
|
+
const greenIds = chain_green_ids(ev);
|
|
1207
|
+
if (greenIds.length === 0)
|
|
1208
|
+
return false;
|
|
1209
|
+
const greenRuns = greenIds.map((greenId) => live_pass(evidences, { gate: "task_complete", kind: "test_run", task_id: taskId }).find((item) => (String(item.evidence_id ?? "") === greenId
|
|
1210
|
+
&& item.semantic_status === "expected_success"
|
|
1211
|
+
&& (item.phase === undefined || item.phase === "green")
|
|
1212
|
+
&& item.apply_execution_chain === "executor_worker"
|
|
1213
|
+
&& item.apply_worker_chain_id === chainId
|
|
1214
|
+
&& executor_worker_test_run_refs_valid(changeRoot, item, taskId, chainId)))).filter((item) => isObject(item));
|
|
1215
|
+
if (greenRuns.length !== greenIds.length)
|
|
1216
|
+
return false;
|
|
1217
|
+
if (!report_input_refs_match(ev.verifier_report_ref, [
|
|
1218
|
+
ev.executor_report_ref,
|
|
1219
|
+
ev.task_code_review_report_ref,
|
|
1220
|
+
...greenRuns.map((green) => ({ kind: "test_run", evidence_id: String(green.evidence_id ?? ""), phase: green.phase, semantic_status: green.semantic_status })),
|
|
1221
|
+
...active_pre_edit_input_refs(evidences, active),
|
|
1222
|
+
]))
|
|
1223
|
+
return false;
|
|
1224
|
+
const currentFreshness = compute_apply_worker_freshness(repoRoot, changeRoot, evidences, taskId, chainId, {
|
|
1225
|
+
executor_report_ref: ev.executor_report_ref,
|
|
1226
|
+
task_code_review_report_ref: ev.task_code_review_report_ref,
|
|
1227
|
+
green_test_run_evidence_ref: greenIds[0],
|
|
1228
|
+
green_test_run_evidence_refs: greenIds,
|
|
1229
|
+
verifier_report_ref: ev.verifier_report_ref,
|
|
1230
|
+
});
|
|
1231
|
+
if (Array.isArray(currentFreshness.unexpected_guard_owned_dirty_paths) && currentFreshness.unexpected_guard_owned_dirty_paths.length > 0)
|
|
1232
|
+
return false;
|
|
1233
|
+
if (Array.isArray(currentFreshness.protected_dirty_paths) && currentFreshness.protected_dirty_paths.length > 0)
|
|
1234
|
+
return false;
|
|
1235
|
+
if (!greenRuns.every((green) => fingerprint_matches(green.implementation_fingerprint, currentFreshness.implementation_fingerprint)))
|
|
1236
|
+
return false;
|
|
1237
|
+
if (!fingerprint_matches(isObject(ev.task_code_review_report_ref) ? ev.task_code_review_report_ref.observed_implementation_fingerprint : undefined, currentFreshness.implementation_fingerprint))
|
|
1238
|
+
return false;
|
|
1239
|
+
return fingerprint_matches(currentFreshness, verifierObserved);
|
|
1240
|
+
}
|
|
1241
|
+
function pinned_takeover_baseline_valid(changeRoot, refItem, taskId, chainId) {
|
|
1242
|
+
return shared_pinned_artifact_ref_reasons(changeRoot, refItem, "serial_takeover_baseline_ref", { kind: "status_report", role: "verifier", taskId, chainId }).length === 0;
|
|
1243
|
+
}
|
|
1244
|
+
function abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, active, taskId, chainId) {
|
|
1245
|
+
if (!active)
|
|
1246
|
+
return false;
|
|
1247
|
+
if ("restored_implementation_fingerprint" in ev) {
|
|
1248
|
+
const activeScope = Array.isArray(active.declared_task_write_scope) ? active.declared_task_write_scope.map(String).filter(Boolean) : [];
|
|
1249
|
+
const current = apply_worker_implementation_fingerprint(repoRoot, changeRoot, [], { declaredTaskWriteScope: activeScope });
|
|
1250
|
+
if (fingerprint_matches(ev.restored_implementation_fingerprint, active.source_implementation_fingerprint)
|
|
1251
|
+
&& fingerprint_matches(current, active.source_implementation_fingerprint))
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
if ("serial_takeover_baseline_ref" in ev) {
|
|
1255
|
+
return pinned_takeover_baseline_valid(changeRoot, ev.serial_takeover_baseline_ref, taskId, chainId)
|
|
1256
|
+
&& Array.isArray(ev.successor_green_evidence_refs)
|
|
1257
|
+
&& ev.successor_green_evidence_refs.length > 0
|
|
1258
|
+
&& ev.successor_green_evidence_refs.every((item) => typeof item === "string" && item.length > 0);
|
|
1259
|
+
}
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
function serial_completion_green(ev) {
|
|
1263
|
+
return (ev.runner_origin === undefined || ev.runner_origin === "main-thread")
|
|
1264
|
+
&& ev.apply_execution_chain !== "executor_worker"
|
|
1265
|
+
&& typeof ev.apply_worker_chain_id !== "string";
|
|
1266
|
+
}
|
|
1267
|
+
function apply_worker_chain_completion_state(repoRoot, changeRoot, evidences, taskId) {
|
|
1268
|
+
const chains = find_pass(evidences, { gate: "task_complete", kind: "apply_worker_chain", task_id: taskId });
|
|
1269
|
+
const activeByChain = new Map();
|
|
1270
|
+
const activeCounts = new Map();
|
|
1271
|
+
for (const ev of chains) {
|
|
1272
|
+
if (String(ev.chain_state ?? "") !== "active")
|
|
1273
|
+
continue;
|
|
1274
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1275
|
+
if (!chainId)
|
|
1276
|
+
continue;
|
|
1277
|
+
const bucket = activeCounts.get(chainId) ?? [];
|
|
1278
|
+
bucket.push(ev);
|
|
1279
|
+
activeCounts.set(chainId, bucket);
|
|
1280
|
+
if (!activeByChain.has(chainId))
|
|
1281
|
+
activeByChain.set(chainId, ev);
|
|
1282
|
+
}
|
|
1283
|
+
const chainIdsWithActive = new Set([...activeByChain.keys()]);
|
|
1284
|
+
const chainIdsWithTerminal = new Set(chains
|
|
1285
|
+
.filter((ev) => {
|
|
1286
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1287
|
+
if (!chainId)
|
|
1288
|
+
return false;
|
|
1289
|
+
if (String(ev.chain_state ?? "") === "closed")
|
|
1290
|
+
return closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1291
|
+
if (String(ev.chain_state ?? "") === "abandoned")
|
|
1292
|
+
return abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
1293
|
+
return false;
|
|
1294
|
+
})
|
|
1295
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
1296
|
+
.filter(Boolean));
|
|
1297
|
+
const chainIdsWithOpenActive = new Set([...activeByChain.keys()].filter((chainId) => !chainIdsWithTerminal.has(chainId)));
|
|
1298
|
+
const validClosedChainIds = new Set(chains
|
|
1299
|
+
.filter((ev) => {
|
|
1300
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1301
|
+
return chainId
|
|
1302
|
+
&& String(ev.chain_state ?? "") === "closed"
|
|
1303
|
+
&& closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1304
|
+
})
|
|
1305
|
+
.map((ev) => String(ev.apply_worker_chain_id ?? ""))
|
|
1306
|
+
.filter(Boolean));
|
|
1307
|
+
const validClosedGreenEvidenceIds = new Set(chains
|
|
1308
|
+
.filter((ev) => {
|
|
1309
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1310
|
+
return chainId
|
|
1311
|
+
&& String(ev.chain_state ?? "") === "closed"
|
|
1312
|
+
&& closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1313
|
+
})
|
|
1314
|
+
.flatMap(chain_green_ids));
|
|
1315
|
+
const takeoverBaselines = [];
|
|
1316
|
+
const reasons = [];
|
|
1317
|
+
const duplicateActives = [...activeCounts.entries()]
|
|
1318
|
+
.filter(([chainId]) => chainIdsWithOpenActive.has(chainId))
|
|
1319
|
+
.map(([, items]) => items)
|
|
1320
|
+
.filter((items) => items.length > 1)
|
|
1321
|
+
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
1322
|
+
.filter(Boolean)
|
|
1323
|
+
.sort();
|
|
1324
|
+
const openActiveValues = [...chainIdsWithOpenActive].map((chainId) => activeByChain.get(chainId)).filter((ev) => isObject(ev));
|
|
1325
|
+
const parallelActives = openActiveValues.length > 1
|
|
1326
|
+
? openActiveValues.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")).filter(Boolean).sort()
|
|
1327
|
+
: [];
|
|
1328
|
+
const activeConflicts = [...new Set([...duplicateActives, ...parallelActives])].sort();
|
|
1329
|
+
if (activeConflicts.length > 0) {
|
|
1330
|
+
reasons.push(reason("apply_worker_chain_active_conflict", `task ${taskId} has conflicting active apply worker chain markers: ${renderList(activeConflicts)}`, activeConflicts));
|
|
1331
|
+
}
|
|
1332
|
+
const terminalsByChain = new Map();
|
|
1333
|
+
for (const ev of chains) {
|
|
1334
|
+
const state = String(ev.chain_state ?? "");
|
|
1335
|
+
if (state !== "closed" && state !== "abandoned")
|
|
1336
|
+
continue;
|
|
1337
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1338
|
+
if (!chainId)
|
|
1339
|
+
continue;
|
|
1340
|
+
const bucket = terminalsByChain.get(chainId) ?? [];
|
|
1341
|
+
bucket.push(ev);
|
|
1342
|
+
terminalsByChain.set(chainId, bucket);
|
|
1343
|
+
}
|
|
1344
|
+
const duplicateTerminals = [...terminalsByChain.values()]
|
|
1345
|
+
.filter((items) => items.length > 1)
|
|
1346
|
+
.flatMap((items) => items.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? "")))
|
|
1347
|
+
.filter(Boolean)
|
|
1348
|
+
.sort();
|
|
1349
|
+
if (duplicateTerminals.length > 0) {
|
|
1350
|
+
reasons.push(reason("apply_worker_chain_terminal_conflict", `task ${taskId} has duplicate apply worker chain terminal markers: ${renderList(duplicateTerminals)}`, duplicateTerminals));
|
|
1351
|
+
}
|
|
1352
|
+
const invalidTerminals = chains
|
|
1353
|
+
.filter((ev) => String(ev.chain_state ?? "") === "closed" || String(ev.chain_state ?? "") === "abandoned")
|
|
1354
|
+
.filter((ev) => {
|
|
1355
|
+
const chainId = String(ev.apply_worker_chain_id ?? "");
|
|
1356
|
+
if (!chainId)
|
|
1357
|
+
return true;
|
|
1358
|
+
if (String(ev.chain_state ?? "") === "closed")
|
|
1359
|
+
return !closed_apply_worker_chain_valid(repoRoot, changeRoot, ev, evidences, taskId, chainId);
|
|
1360
|
+
const valid = abandoned_apply_worker_chain_valid(repoRoot, changeRoot, ev, activeByChain.get(chainId), taskId, chainId);
|
|
1361
|
+
if (valid && isObject(ev.serial_takeover_baseline_ref)) {
|
|
1362
|
+
const baseline = read_pinned_artifact_json(changeRoot, ev.serial_takeover_baseline_ref);
|
|
1363
|
+
if (baseline?.implementation_fingerprint !== undefined) {
|
|
1364
|
+
takeoverBaselines.push({
|
|
1365
|
+
fingerprint: baseline.implementation_fingerprint,
|
|
1366
|
+
successorGreenRefs: Array.isArray(ev.successor_green_evidence_refs) ? ev.successor_green_evidence_refs.map(String).filter(Boolean) : [],
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return !valid;
|
|
1371
|
+
})
|
|
1372
|
+
.map((ev) => String(ev.evidence_id ?? ev.apply_worker_chain_id ?? ""))
|
|
1373
|
+
.filter(Boolean)
|
|
1374
|
+
.sort();
|
|
1375
|
+
if (invalidTerminals.length > 0) {
|
|
1376
|
+
reasons.push(reason("apply_worker_chain_terminal_invalid", `task ${taskId} has invalid apply worker chain terminal markers: ${renderList(invalidTerminals)}`, invalidTerminals));
|
|
1377
|
+
}
|
|
1378
|
+
for (const chainId of [...chainIdsWithOpenActive].sort()) {
|
|
1379
|
+
if (!chainIdsWithTerminal.has(chainId)) {
|
|
1380
|
+
reasons.push(reason("apply_worker_chain_active", `task ${taskId} has active apply worker chain and cannot use non-chain task completion: ${chainId}`, [chainId]));
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
for (const ev of evidences) {
|
|
1384
|
+
if (!isObject(ev) || ev._invalid || ev.status !== "pass" || ev.task_id !== taskId)
|
|
1385
|
+
continue;
|
|
1386
|
+
const chainId = typeof ev.apply_worker_chain_id === "string" ? ev.apply_worker_chain_id : "";
|
|
1387
|
+
if (!chainId)
|
|
1388
|
+
continue;
|
|
1389
|
+
if (!chainIdsWithActive.has(chainId)) {
|
|
1390
|
+
reasons.push(reason("apply_worker_chain_missing_active", `task ${taskId} references apply_worker_chain_id=${repr(chainId)} without a matching active marker`, [chainId]));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (duplicateTerminals.length > 0 || activeConflicts.length > 0) {
|
|
1394
|
+
validClosedChainIds.clear();
|
|
1395
|
+
validClosedGreenEvidenceIds.clear();
|
|
1396
|
+
}
|
|
1397
|
+
return { reasons, validClosedChainIds, validClosedGreenEvidenceIds, takeoverBaselines };
|
|
1398
|
+
}
|
|
1399
|
+
export function apply_worker_chain_lifecycle_reasons(repoRoot, changeRoot, evidences, taskId) {
|
|
1400
|
+
return apply_worker_chain_completion_state(repoRoot, changeRoot, evidences, taskId).reasons;
|
|
1401
|
+
}
|
|
1163
1402
|
export function check_task_complete(change, status, changeRoot, evidences, taskId) {
|
|
1164
1403
|
const gate = "task_complete";
|
|
1165
1404
|
const reasons = [];
|
|
@@ -1174,6 +1413,8 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1174
1413
|
const task = parse_tasks(changeRoot)[taskId];
|
|
1175
1414
|
if (!task)
|
|
1176
1415
|
return block(change, gate, [reason("unknown_task", `task ${taskId} not found`)], { task_id: taskId });
|
|
1416
|
+
const workerChainState = apply_worker_chain_completion_state(get_repo_root(status), changeRoot, evidences, taskId);
|
|
1417
|
+
reasons.push(...workerChainState.reasons);
|
|
1177
1418
|
const reopenHistory = task_reopen_history(evidences, taskId);
|
|
1178
1419
|
if (task.checked && reopenHistory.pass.length > 1) {
|
|
1179
1420
|
reasons.push(reason("reopen_lifecycle_exhausted", `task ${taskId} has multiple task_reopen histories in this change; v1 allows at most one`, [taskId]));
|
|
@@ -1193,9 +1434,22 @@ export function check_task_complete(change, status, changeRoot, evidences, taskI
|
|
|
1193
1434
|
if (!TDD_MODES.has(tddMode))
|
|
1194
1435
|
reasons.push(reason("invalid_tdd_mode", `task ${taskId}: tdd_mode=${repr(tddMode)}`));
|
|
1195
1436
|
if (tddRequired) {
|
|
1196
|
-
const
|
|
1437
|
+
const allGreen = task_test_evidence(evidences, taskId, "expected_success", "task_complete");
|
|
1438
|
+
const green = workerChainState.validClosedGreenEvidenceIds.size > 0
|
|
1439
|
+
? allGreen.filter((ev) => (ev.apply_execution_chain === "executor_worker"
|
|
1440
|
+
&& workerChainState.validClosedChainIds.has(String(ev.apply_worker_chain_id ?? ""))
|
|
1441
|
+
&& workerChainState.validClosedGreenEvidenceIds.has(String(ev.evidence_id ?? ""))))
|
|
1442
|
+
: workerChainState.takeoverBaselines.length > 0
|
|
1443
|
+
? allGreen.filter((ev) => serial_completion_green(ev) && workerChainState.takeoverBaselines.some((baseline) => (baseline.successorGreenRefs.includes(String(ev.evidence_id ?? ""))
|
|
1444
|
+
&& fingerprint_matches(ev.implementation_fingerprint, baseline.fingerprint))))
|
|
1445
|
+
: allGreen.filter(serial_completion_green);
|
|
1197
1446
|
if (green.length === 0)
|
|
1198
1447
|
reasons.push(reason("missing_green_evidence", `task ${taskId} requires GREEN evidence (expected_success) before completion`));
|
|
1448
|
+
if (workerChainState.validClosedGreenEvidenceIds.size === 0 && workerChainState.takeoverBaselines.length > 0) {
|
|
1449
|
+
if (green.length === 0) {
|
|
1450
|
+
reasons.push(reason("apply_worker_chain_takeover_green_mismatch", `task ${taskId} requires declared successor serial GREEN evidence after takeover baseline with matching implementation fingerprint`));
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1199
1453
|
const declared = new Set(splitList(attrs.test_refs ?? ""));
|
|
1200
1454
|
const declaredInvariants = new Set(splitList(attrs.invariant_refs ?? ""));
|
|
1201
1455
|
if (declared.size === 0)
|
package/dist/src/i18n.js
CHANGED
|
@@ -9,6 +9,9 @@ const COMMAND_ZH = {
|
|
|
9
9
|
"check-task-reopen": { label_zh: "任务重开检查", hint_zh: "检查被审查打回的任务是否满足重开条件。" },
|
|
10
10
|
"check-task-edit": { label_zh: "任务编辑前检查", hint_zh: "检查任务在开始实现前是否满足测试与范围前置条件。" },
|
|
11
11
|
"check-task-complete": { label_zh: "任务完成检查", hint_zh: "检查任务在勾完成前是否满足 GREEN 或替代验证要求。" },
|
|
12
|
+
"workflow-packet": { label_zh: "流程数据包", hint_zh: "只读生成当前流程 gate 的紧凑执行数据包。" },
|
|
13
|
+
"review-packet": { label_zh: "审查数据包", hint_zh: "只读生成审查角色或主线程的最小任务包。" },
|
|
14
|
+
"ledger-render": { label_zh: "问题清单渲染", hint_zh: "渲染审查轮次的确定性问题清单文本。" },
|
|
12
15
|
"check-review-ready": { label_zh: "进入审查前检查", hint_zh: "检查实现阶段是否已经处理完成,可以进入审查阶段。" },
|
|
13
16
|
"check-review-complete": { label_zh: "审查完成检查", hint_zh: "检查审查阶段证据是否齐备并允许通过。" },
|
|
14
17
|
"check-verify-ready": { label_zh: "验证完成检查", hint_zh: "检查最终验证证据是否完整。" },
|
|
@@ -583,10 +586,8 @@ export function action_status_zh(status) {
|
|
|
583
586
|
}
|
|
584
587
|
export function action_label_zh(action) {
|
|
585
588
|
const exact = {
|
|
586
|
-
|
|
589
|
+
openspec_cli_surface: "检查 OpenSpec CLI 能力",
|
|
587
590
|
superspec_repo_local_roles: "检查 SuperSpec 本地角色与提示词",
|
|
588
|
-
"openspec init --tools codex .": "运行 OpenSpec 初始化命令",
|
|
589
|
-
"openspec update --force .": "运行 OpenSpec 更新命令",
|
|
590
591
|
};
|
|
591
592
|
if (exact[action])
|
|
592
593
|
return exact[action];
|
|
@@ -16,6 +16,11 @@ export type ManifestFileEntry = {
|
|
|
16
16
|
managed: boolean;
|
|
17
17
|
preexisting: boolean;
|
|
18
18
|
};
|
|
19
|
+
export type ManifestConfigPatchEntry = {
|
|
20
|
+
path: string;
|
|
21
|
+
retainedOnUninstall: boolean;
|
|
22
|
+
managed: boolean;
|
|
23
|
+
};
|
|
19
24
|
export type EngineAction = {
|
|
20
25
|
action: string;
|
|
21
26
|
status: "ok" | "created" | "updated" | "skipped" | "removed" | "would_remove" | "failed";
|
|
@@ -27,6 +32,8 @@ export type EngineResult = {
|
|
|
27
32
|
problems: string[];
|
|
28
33
|
manifest: JsonMap | null;
|
|
29
34
|
};
|
|
35
|
+
export declare const CODEX_NATIVE_AGENT_MAX_THREADS = 12;
|
|
36
|
+
export declare const CODEX_NATIVE_AGENT_MAX_DEPTH = 1;
|
|
30
37
|
export declare function load_install_map(packageRoot?: string): {
|
|
31
38
|
mappings: InstallMapping[];
|
|
32
39
|
problems: string[];
|
|
@@ -39,6 +46,16 @@ export declare function read_install_manifest(repoRoot: string, opts?: {
|
|
|
39
46
|
problems: string[];
|
|
40
47
|
};
|
|
41
48
|
export declare function install_manifest_rel(scope?: InstallScope): string;
|
|
49
|
+
export declare function codex_config_rel(scope?: InstallScope): string;
|
|
50
|
+
export declare function merge_codex_config(text: string, opts?: {
|
|
51
|
+
force?: boolean;
|
|
52
|
+
}): string;
|
|
53
|
+
export declare function ensure_codex_config(repoRoot: string, scope?: InstallScope, opts?: {
|
|
54
|
+
force?: boolean;
|
|
55
|
+
}): {
|
|
56
|
+
action: EngineAction;
|
|
57
|
+
problems: string[];
|
|
58
|
+
};
|
|
42
59
|
export declare function install_workflow(repoRoot: string, opts?: {
|
|
43
60
|
force?: boolean;
|
|
44
61
|
packageRoot?: string;
|
|
@@ -21,6 +21,14 @@ export const PACKAGE_ROOT = find_package_root(import.meta.url);
|
|
|
21
21
|
export const PROJECT_INSTALL_MANIFEST_REL = join(".codex", "superspec", "install-manifest.json");
|
|
22
22
|
export const USER_INSTALL_MANIFEST_REL = join("superspec", "install-manifest.json");
|
|
23
23
|
export const INSTALL_MANIFEST_REL = PROJECT_INSTALL_MANIFEST_REL;
|
|
24
|
+
export const CODEX_NATIVE_AGENT_MAX_THREADS = 12;
|
|
25
|
+
export const CODEX_NATIVE_AGENT_MAX_DEPTH = 1;
|
|
26
|
+
const CODEX_CONFIG_ENTRIES = [
|
|
27
|
+
{ table: "features", key: "multi_agent", value: "true" },
|
|
28
|
+
{ table: "features", key: "child_agents_md", value: "true" },
|
|
29
|
+
{ table: "agents", key: "max_threads", value: String(CODEX_NATIVE_AGENT_MAX_THREADS) },
|
|
30
|
+
{ table: "agents", key: "max_depth", value: String(CODEX_NATIVE_AGENT_MAX_DEPTH) },
|
|
31
|
+
];
|
|
24
32
|
function package_version(packageRoot) {
|
|
25
33
|
try {
|
|
26
34
|
const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
@@ -78,6 +86,15 @@ export function manifest_shape_problems(manifest) {
|
|
|
78
86
|
problems.push("createdDirs malformed");
|
|
79
87
|
if (!Array.isArray(manifest.dataGlobs) || manifest.dataGlobs.some((item) => typeof item !== "string" || !item))
|
|
80
88
|
problems.push("dataGlobs malformed");
|
|
89
|
+
if (manifest.configPatch !== undefined) {
|
|
90
|
+
if (!isObject(manifest.configPatch)
|
|
91
|
+
|| typeof manifest.configPatch.path !== "string"
|
|
92
|
+
|| !manifest.configPatch.path
|
|
93
|
+
|| manifest.configPatch.retainedOnUninstall !== true
|
|
94
|
+
|| manifest.configPatch.managed !== false) {
|
|
95
|
+
problems.push("configPatch malformed");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
81
98
|
if (!Array.isArray(manifest.files)) {
|
|
82
99
|
problems.push("files missing");
|
|
83
100
|
}
|
|
@@ -128,6 +145,101 @@ function scoped_mappings(mappings, scope) {
|
|
|
128
145
|
return scoped === null ? [] : [scoped];
|
|
129
146
|
});
|
|
130
147
|
}
|
|
148
|
+
export function codex_config_rel(scope = "project") {
|
|
149
|
+
return scope === "user" ? "config.toml" : join(".codex", "config.toml");
|
|
150
|
+
}
|
|
151
|
+
function table_header_name(line) {
|
|
152
|
+
const match = /^\s*\[([A-Za-z0-9_.-]+)\]\s*(?:#.*)?$/u.exec(line);
|
|
153
|
+
return match?.[1] ?? null;
|
|
154
|
+
}
|
|
155
|
+
function is_any_table_header(line) {
|
|
156
|
+
return /^\s*\[[^\]]+\]\s*(?:#.*)?$/u.test(line);
|
|
157
|
+
}
|
|
158
|
+
function escape_regexp(text) {
|
|
159
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
160
|
+
}
|
|
161
|
+
function split_toml_lines(text) {
|
|
162
|
+
if (!text)
|
|
163
|
+
return [];
|
|
164
|
+
const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
165
|
+
if (lines[lines.length - 1] === "")
|
|
166
|
+
lines.pop();
|
|
167
|
+
return lines;
|
|
168
|
+
}
|
|
169
|
+
function ensure_toml_key(lines, table, key, value, overwriteExisting) {
|
|
170
|
+
let start = -1;
|
|
171
|
+
let end = lines.length;
|
|
172
|
+
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
173
|
+
if (table_header_name(lines[idx]) !== table)
|
|
174
|
+
continue;
|
|
175
|
+
start = idx;
|
|
176
|
+
end = lines.length;
|
|
177
|
+
for (let next = idx + 1; next < lines.length; next += 1) {
|
|
178
|
+
if (!is_any_table_header(lines[next]))
|
|
179
|
+
continue;
|
|
180
|
+
end = next;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
if (start === -1) {
|
|
186
|
+
if (lines.length > 0 && lines[lines.length - 1].trim() !== "")
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push(`[${table}]`, `${key} = ${value}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
const keyRe = new RegExp(`^\\s*${escape_regexp(key)}\\s*=`, "u");
|
|
192
|
+
const desired = `${key} = ${value}`;
|
|
193
|
+
for (let idx = start + 1; idx < end; idx += 1) {
|
|
194
|
+
if (!keyRe.test(lines[idx]))
|
|
195
|
+
continue;
|
|
196
|
+
if (lines[idx] === desired)
|
|
197
|
+
return false;
|
|
198
|
+
if (!overwriteExisting)
|
|
199
|
+
return false;
|
|
200
|
+
lines[idx] = desired;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
let insertAt = end;
|
|
204
|
+
while (insertAt > start + 1 && lines[insertAt - 1].trim() === "")
|
|
205
|
+
insertAt -= 1;
|
|
206
|
+
lines.splice(insertAt, 0, desired);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
export function merge_codex_config(text, opts = {}) {
|
|
210
|
+
const lines = split_toml_lines(text);
|
|
211
|
+
for (const entry of CODEX_CONFIG_ENTRIES) {
|
|
212
|
+
ensure_toml_key(lines, entry.table, entry.key, entry.value, opts.force === true);
|
|
213
|
+
}
|
|
214
|
+
return `${lines.join("\n")}\n`;
|
|
215
|
+
}
|
|
216
|
+
export function ensure_codex_config(repoRoot, scope = "project", opts = {}) {
|
|
217
|
+
const rel = codex_config_rel(scope);
|
|
218
|
+
const configPath = join(repoRoot, rel);
|
|
219
|
+
const existed = existsSync(configPath);
|
|
220
|
+
try {
|
|
221
|
+
const before = existed ? readFileSync(configPath, "utf8") : "";
|
|
222
|
+
const after = merge_codex_config(before, opts);
|
|
223
|
+
if (after === before)
|
|
224
|
+
return { action: { action: `configure ${rel}`, status: "ok" }, problems: [] };
|
|
225
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
226
|
+
writeFileSync(configPath, after, "utf8");
|
|
227
|
+
return {
|
|
228
|
+
action: {
|
|
229
|
+
action: `configure ${rel}`,
|
|
230
|
+
status: existed ? "updated" : "created",
|
|
231
|
+
detail: `Codex native subagent concurrency set to ${CODEX_NATIVE_AGENT_MAX_THREADS}`,
|
|
232
|
+
},
|
|
233
|
+
problems: [],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
return {
|
|
238
|
+
action: { action: `configure ${rel}`, status: "failed" },
|
|
239
|
+
problems: [`Codex config update failed: ${err.message}`],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
131
243
|
function write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope) {
|
|
132
244
|
const manifest = {
|
|
133
245
|
superspecVersion: package_version(packageRoot),
|
|
@@ -138,6 +250,11 @@ function write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope
|
|
|
138
250
|
files,
|
|
139
251
|
createdDirs: [...new Set(createdDirs)].sort(),
|
|
140
252
|
dataGlobs: ["**/.superspec"],
|
|
253
|
+
configPatch: {
|
|
254
|
+
path: codex_config_rel(scope),
|
|
255
|
+
retainedOnUninstall: true,
|
|
256
|
+
managed: false,
|
|
257
|
+
},
|
|
141
258
|
};
|
|
142
259
|
const manifestPath = join(repoRoot, install_manifest_rel(scope));
|
|
143
260
|
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
@@ -212,8 +329,11 @@ export function install_workflow(repoRoot, opts = {}) {
|
|
|
212
329
|
actions.push({ action: `install ${mapping.target}`, status: "skipped", detail: "pre-existing file with different content kept; rerun with --force to overwrite (backs up *.bak)" });
|
|
213
330
|
}
|
|
214
331
|
}
|
|
332
|
+
const configResult = ensure_codex_config(repoRoot, scope, { force: opts.force === true });
|
|
333
|
+
actions.push(configResult.action);
|
|
334
|
+
problems.push(...configResult.problems);
|
|
215
335
|
const manifest = write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope);
|
|
216
|
-
return { actions, problems
|
|
336
|
+
return { actions, problems, manifest };
|
|
217
337
|
}
|
|
218
338
|
export function update_workflow(repoRoot, opts = {}) {
|
|
219
339
|
const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
|
|
@@ -287,8 +407,11 @@ export function update_workflow(repoRoot, opts = {}) {
|
|
|
287
407
|
actions.push({ action: `update ${prev.path}`, status: "skipped", detail: "no longer shipped but user-modified; kept" });
|
|
288
408
|
}
|
|
289
409
|
}
|
|
410
|
+
const configResult = ensure_codex_config(repoRoot, scope);
|
|
411
|
+
actions.push(configResult.action);
|
|
412
|
+
problems.push(...configResult.problems);
|
|
290
413
|
const manifest = write_install_manifest(repoRoot, packageRoot, files, createdDirs, scope);
|
|
291
|
-
return { actions, problems
|
|
414
|
+
return { actions, problems, manifest };
|
|
292
415
|
}
|
|
293
416
|
function remove_empty_created_dirs(repoRoot, createdDirs, actions) {
|
|
294
417
|
const byDepth = [...new Set(createdDirs)].sort((a, b) => b.split("/").length - a.split("/").length);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type MeasuredTextFile = {
|
|
2
|
+
path: string;
|
|
3
|
+
chars: number;
|
|
4
|
+
};
|
|
5
|
+
export type SurfaceMeasure = {
|
|
6
|
+
total_chars: number;
|
|
7
|
+
files: MeasuredTextFile[];
|
|
8
|
+
};
|
|
9
|
+
export type RepresentativeScenarioMeasure = {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
total_chars: number;
|
|
13
|
+
files: MeasuredTextFile[];
|
|
14
|
+
};
|
|
15
|
+
export type MaterializedTextSample = {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
chars: number;
|
|
19
|
+
matched_markers?: string[];
|
|
20
|
+
};
|
|
21
|
+
export type MaterializedTextMeasure = {
|
|
22
|
+
total_chars: number;
|
|
23
|
+
samples: MaterializedTextSample[];
|
|
24
|
+
};
|
|
25
|
+
export type PacketMeasureReport = {
|
|
26
|
+
fixed_surface_chars_install_upper_bound: SurfaceMeasure;
|
|
27
|
+
fixed_surface_chars_runtime_required_subset: SurfaceMeasure;
|
|
28
|
+
materialized_workflow_packet_chars: MaterializedTextMeasure;
|
|
29
|
+
materialized_review_packet_chars: MaterializedTextMeasure;
|
|
30
|
+
materialized_review_prompt_chars: MaterializedTextMeasure;
|
|
31
|
+
ledger_block_chars: MaterializedTextMeasure;
|
|
32
|
+
representative_loaded_surface_chars: {
|
|
33
|
+
scenarios: RepresentativeScenarioMeasure[];
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
type ScenarioSpec = {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
files: string[];
|
|
40
|
+
};
|
|
41
|
+
export declare function representative_scenarios(): readonly ScenarioSpec[];
|
|
42
|
+
export declare function measure_packet_surface_report(repoRoot: string): PacketMeasureReport;
|
|
43
|
+
export {};
|