@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.1-beta.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/AGENTS.md +2 -6
- package/CHANGELOG.md +72 -163
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/infrastructure/harness/atomic-write.js +1 -2
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/frontend-project-capability.js +12 -162
- package/dist/task/source-prepare/completeness.js +0 -17
- package/dist/task/source-prepare/parse-intent.js +1 -15
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -61
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -86
- package/dist/worker/console/chat/routes.js +8 -36
- package/dist/worker/console/chat/session-store.js +0 -3
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -97
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +13 -373
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -89
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +11 -30
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +62 -394
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +86 -238
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -105
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +12 -314
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -132
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/frontend-design-contract.md +14 -34
- package/docs/templates/frontend-task-constraints.md +13 -33
- package/docs/templates/frontend-task-requirement.md +20 -54
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -4
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -3
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -471
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -13,13 +13,6 @@ const CONTROLLER_PROTOCOL_CAPABILITIES = [
|
|
|
13
13
|
"worker-attempt-revision-v1",
|
|
14
14
|
"recovery-decision-v2",
|
|
15
15
|
];
|
|
16
|
-
function officialModelCallable(coverage, kind) {
|
|
17
|
-
if (coverage === "human-gated-required")
|
|
18
|
-
return "prepare-only";
|
|
19
|
-
if (coverage === "advanced" && kind !== "read")
|
|
20
|
-
return "never";
|
|
21
|
-
return "always";
|
|
22
|
-
}
|
|
23
16
|
const officialAction = (action, cli, kind, coverage) => ({
|
|
24
17
|
action,
|
|
25
18
|
cli,
|
|
@@ -58,7 +51,11 @@ const officialAction = (action, cli, kind, coverage) => ({
|
|
|
58
51
|
]
|
|
59
52
|
: []),
|
|
60
53
|
],
|
|
61
|
-
modelCallable:
|
|
54
|
+
modelCallable: coverage === "human-gated-required"
|
|
55
|
+
? "prepare-only"
|
|
56
|
+
: coverage === "advanced" && kind !== "read"
|
|
57
|
+
? "never"
|
|
58
|
+
: "always",
|
|
62
59
|
humanConfirmation: coverage === "human-gated-required" ? "required" : "none",
|
|
63
60
|
});
|
|
64
61
|
const OFFICIAL_ACTIONS = [
|
|
@@ -145,13 +142,13 @@ const OFFICIAL_ACTIONS = [
|
|
|
145
142
|
["stats", "loop-agent stats", "read", "model-callable"],
|
|
146
143
|
[
|
|
147
144
|
"promoteRun",
|
|
148
|
-
"loop-agent task advance",
|
|
145
|
+
"loop-agent task advance <id> --json",
|
|
149
146
|
"mutation",
|
|
150
147
|
"model-callable",
|
|
151
148
|
],
|
|
152
149
|
[
|
|
153
150
|
"closeoutTask",
|
|
154
|
-
"loop-agent task advance",
|
|
151
|
+
"loop-agent task advance <id> --json",
|
|
155
152
|
"mutation",
|
|
156
153
|
"model-callable",
|
|
157
154
|
],
|
|
@@ -448,49 +445,6 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
448
445
|
modelCallable: "always",
|
|
449
446
|
humanConfirmation: "none",
|
|
450
447
|
},
|
|
451
|
-
{
|
|
452
|
-
action: "operationWait",
|
|
453
|
-
cli: "console canonical operation wait (read-only event-driven long poll)",
|
|
454
|
-
kind: "read",
|
|
455
|
-
inputSchemaVersion: 1,
|
|
456
|
-
resultSchemaVersion: 1,
|
|
457
|
-
resultPolicy: { readOnly: true, bounded: true, redacted: true },
|
|
458
|
-
envelopeSchemaVersion: 1,
|
|
459
|
-
requiredErrorCodes: [
|
|
460
|
-
"NOT_FOUND",
|
|
461
|
-
"INVALID_INPUT",
|
|
462
|
-
"EVENT_CURSOR_EXPIRED",
|
|
463
|
-
],
|
|
464
|
-
description: "Read-only event-driven wait on the canonical operation event ring. Default wakeOn=meaningful: heartbeats never settle the wait and are never returned as event payload (nextSeq still covers them); state/result/stdout/stderr/reconcile/error settle immediately; terminal or needs-reconcile flushes immediately. Every subsequent wait MUST pass afterSeq equal to the previous response nextSeq; omitting or reusing an older cursor replays already-consumed startup events. The response includes recommendedNextCall with the safe cursor. wakeOn=all (diagnostic compatibility) resolves on the first new event including heartbeats. maxWaitMs elapsing returns timedOut:true (a success summary, not a command failure).",
|
|
465
|
-
inputParams: [
|
|
466
|
-
{
|
|
467
|
-
name: "operationId",
|
|
468
|
-
type: "string",
|
|
469
|
-
required: true,
|
|
470
|
-
description: "canonical operation id",
|
|
471
|
-
},
|
|
472
|
-
{
|
|
473
|
-
name: "afterSeq",
|
|
474
|
-
type: "number",
|
|
475
|
-
required: false,
|
|
476
|
-
description: "event cursor; first wait may use 0, every later wait MUST use the previous response nextSeq (>= 0)",
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
name: "maxWaitMs",
|
|
480
|
-
type: "number",
|
|
481
|
-
required: false,
|
|
482
|
-
description: "bounded wait budget; clamped server-side (default 180000, floor 60000)",
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
name: "wakeOn",
|
|
486
|
-
type: "string",
|
|
487
|
-
required: false,
|
|
488
|
-
description: "wake policy: meaningful (default; heartbeats are cursor-only liveness) | all (first event settles, diagnostics)",
|
|
489
|
-
},
|
|
490
|
-
],
|
|
491
|
-
modelCallable: "always",
|
|
492
|
-
humanConfirmation: "none",
|
|
493
|
-
},
|
|
494
448
|
{
|
|
495
449
|
action: "contractShow",
|
|
496
450
|
cli: "loop-agent task status <taskId> --json",
|
|
@@ -942,74 +896,6 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
942
896
|
modelCallable: "always",
|
|
943
897
|
humanConfirmation: "none",
|
|
944
898
|
},
|
|
945
|
-
{
|
|
946
|
-
action: "taskAdvance",
|
|
947
|
-
cli: "loop-agent task advance <taskId> [title] --task-kind <kind> --allowed-path <glob> --forbidden-path <glob> --verify <label:command> --json",
|
|
948
|
-
kind: "long-running",
|
|
949
|
-
inputSchemaVersion: 1,
|
|
950
|
-
resultSchemaVersion: 1,
|
|
951
|
-
envelopeSchemaVersion: 1,
|
|
952
|
-
requiredErrorCodes: [
|
|
953
|
-
...COMMON_MUTATION_ERRORS,
|
|
954
|
-
"INVALID_INPUT",
|
|
955
|
-
],
|
|
956
|
-
description: "Canonical typed task advance mutation. Use after task creation/PRD import to apply explicit taskKind and engineering boundaries, generate + strict-validate the DAG, and stop at the writeSet gate. Never pass placeholder ids such as <id>. A successful child process may still return a blocked business outcome; inspect lifecycleState, blockers, gate, and next.",
|
|
957
|
-
inputParams: [
|
|
958
|
-
{
|
|
959
|
-
name: "taskId",
|
|
960
|
-
type: "string",
|
|
961
|
-
required: true,
|
|
962
|
-
description: "canonical YYYY-MM-DD-<slug> task id; placeholders are rejected",
|
|
963
|
-
},
|
|
964
|
-
{
|
|
965
|
-
name: "title",
|
|
966
|
-
type: "string",
|
|
967
|
-
required: false,
|
|
968
|
-
description: "optional task title for create/first advance",
|
|
969
|
-
},
|
|
970
|
-
{
|
|
971
|
-
name: "taskKind",
|
|
972
|
-
type: "string",
|
|
973
|
-
required: false,
|
|
974
|
-
description: "business DAG template; ordinary fixes/docs use standard",
|
|
975
|
-
},
|
|
976
|
-
{
|
|
977
|
-
name: "featureId",
|
|
978
|
-
type: "string",
|
|
979
|
-
required: false,
|
|
980
|
-
description: "required only by feature-scoped task kinds such as knowledge-sync",
|
|
981
|
-
},
|
|
982
|
-
{
|
|
983
|
-
name: "profile",
|
|
984
|
-
type: "string",
|
|
985
|
-
required: false,
|
|
986
|
-
description: "governance profile auto|minimal|standard|reviewed|supervised",
|
|
987
|
-
},
|
|
988
|
-
{
|
|
989
|
-
name: "allowedPaths",
|
|
990
|
-
type: "array",
|
|
991
|
-
itemsType: "string",
|
|
992
|
-
required: false,
|
|
993
|
-
description: "explicit writer boundary globs",
|
|
994
|
-
},
|
|
995
|
-
{
|
|
996
|
-
name: "forbiddenPaths",
|
|
997
|
-
type: "array",
|
|
998
|
-
itemsType: "string",
|
|
999
|
-
required: false,
|
|
1000
|
-
description: "explicit forbidden path globs",
|
|
1001
|
-
},
|
|
1002
|
-
{
|
|
1003
|
-
name: "verifyCommands",
|
|
1004
|
-
type: "array",
|
|
1005
|
-
itemsType: "string",
|
|
1006
|
-
required: false,
|
|
1007
|
-
description: "verification entries as label:command strings",
|
|
1008
|
-
},
|
|
1009
|
-
],
|
|
1010
|
-
modelCallable: "always",
|
|
1011
|
-
humanConfirmation: "none",
|
|
1012
|
-
},
|
|
1013
899
|
{
|
|
1014
900
|
action: "dagRunTask",
|
|
1015
901
|
cli: "loop-agent task advance <taskId> --profile auto --dag-output <path> --json",
|
|
@@ -1079,7 +965,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1079
965
|
"CONTROLLER_MISMATCH",
|
|
1080
966
|
"INVALID_INPUT",
|
|
1081
967
|
],
|
|
1082
|
-
description: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor
|
|
968
|
+
description: "Consume a single-use execution receipt and execute the reviewed DAG (prefer task advance --approve-gate when gate token present). accepted/queued/running/operationId are NOT completion — supervise via operationGet/status/dagReport/dagDoctor.",
|
|
1083
969
|
inputParams: [
|
|
1084
970
|
{
|
|
1085
971
|
name: "executionId",
|
|
@@ -1102,7 +988,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1102
988
|
resultSchemaVersion: 1,
|
|
1103
989
|
envelopeSchemaVersion: 1,
|
|
1104
990
|
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1105
|
-
description: "Read-only R1 subgraph plan (no mutation). Call before dagRerun. Prefer primaryFailure.nodeId from dagReport. If eligible=false (writer/decision/fingerprint
|
|
991
|
+
description: "Read-only R1 subgraph plan (no mutation). Call before dagRerun. Prefer primaryFailure.nodeId from dagReport. If eligible=false (writer/decision/fingerprint), do not force execute — fall back to standaloneTaskRerun or same-task advance. Returns planHash required by execute.",
|
|
1106
992
|
inputParams: [
|
|
1107
993
|
{
|
|
1108
994
|
name: "runId",
|
|
@@ -1165,73 +1051,6 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1165
1051
|
modelCallable: "always",
|
|
1166
1052
|
humanConfirmation: "none",
|
|
1167
1053
|
},
|
|
1168
|
-
{
|
|
1169
|
-
action: "dagInterruptEligibility",
|
|
1170
|
-
cli: "console dag-interrupt-eligibility",
|
|
1171
|
-
kind: "read",
|
|
1172
|
-
inputSchemaVersion: 1,
|
|
1173
|
-
resultSchemaVersion: 1,
|
|
1174
|
-
envelopeSchemaVersion: 1,
|
|
1175
|
-
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1176
|
-
description: "Read-only eligibility for cooperative live-DAG interrupt. v1 only Console-owned active running operations. Worker-managed / unknown owner / orphaned / terminal runs return eligible=false with stable reasonCodes. Does not create a Human Gate receipt or mutate the run.",
|
|
1177
|
-
inputParams: [
|
|
1178
|
-
{
|
|
1179
|
-
name: "runId",
|
|
1180
|
-
type: "string",
|
|
1181
|
-
required: true,
|
|
1182
|
-
description: "active dag run id to inspect for interrupt eligibility",
|
|
1183
|
-
},
|
|
1184
|
-
],
|
|
1185
|
-
modelCallable: "always",
|
|
1186
|
-
humanConfirmation: "none",
|
|
1187
|
-
},
|
|
1188
|
-
{
|
|
1189
|
-
action: "dagRequestInterrupt",
|
|
1190
|
-
cli: "loop-agent dag request-interrupt --run-id <id> --request-id <id> --target-operation-id <id> --reason-code <code> --reason <detail> --json",
|
|
1191
|
-
kind: "mutation",
|
|
1192
|
-
inputSchemaVersion: 1,
|
|
1193
|
-
resultSchemaVersion: 1,
|
|
1194
|
-
envelopeSchemaVersion: 1,
|
|
1195
|
-
requiredErrorCodes: [
|
|
1196
|
-
...COMMON_MUTATION_ERRORS,
|
|
1197
|
-
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1198
|
-
"RUN_NOT_ACTIVE",
|
|
1199
|
-
"RUN_NOT_CONSOLE_OWNED",
|
|
1200
|
-
"RUNNER_IDENTITY_MISMATCH",
|
|
1201
|
-
"INTERRUPT_ALREADY_PENDING",
|
|
1202
|
-
"RUN_ALREADY_TERMINAL",
|
|
1203
|
-
"INTERRUPT_NEEDS_RECONCILE",
|
|
1204
|
-
],
|
|
1205
|
-
description: "Request cooperative abort of a Console-owned running DAG. Success means interrupt.json is requested, not that the run has already stopped. Browser Human Gate only; model must not call this action.",
|
|
1206
|
-
inputParams: [
|
|
1207
|
-
{
|
|
1208
|
-
name: "runId",
|
|
1209
|
-
type: "string",
|
|
1210
|
-
required: true,
|
|
1211
|
-
description: "active dag run id",
|
|
1212
|
-
},
|
|
1213
|
-
{
|
|
1214
|
-
name: "reasonCode",
|
|
1215
|
-
type: "string",
|
|
1216
|
-
required: true,
|
|
1217
|
-
description: "requirements-changed | suspected-runaway | resource-protection | operator-request | other",
|
|
1218
|
-
},
|
|
1219
|
-
{
|
|
1220
|
-
name: "reasonDetail",
|
|
1221
|
-
type: "string",
|
|
1222
|
-
required: true,
|
|
1223
|
-
description: "10–500 character operator reason; do not auto-fill placeholders",
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
name: "confirmationId",
|
|
1227
|
-
type: "string",
|
|
1228
|
-
required: true,
|
|
1229
|
-
description: "human confirmation receipt id from prepareMutationGate",
|
|
1230
|
-
},
|
|
1231
|
-
],
|
|
1232
|
-
modelCallable: "prepare-only",
|
|
1233
|
-
humanConfirmation: "required",
|
|
1234
|
-
},
|
|
1235
1054
|
{
|
|
1236
1055
|
action: "prepareMutationGate",
|
|
1237
1056
|
cli: "console prepare-mutation-gate",
|
|
@@ -1240,13 +1059,13 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1240
1059
|
resultSchemaVersion: 1,
|
|
1241
1060
|
envelopeSchemaVersion: 1,
|
|
1242
1061
|
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1243
|
-
description: "Prepare a one-shot Human Gate receipt for
|
|
1062
|
+
description: "Prepare a one-shot Human Gate receipt for Night Scheduler mutations. Model may prepare; only the browser mutation gate may consume.",
|
|
1244
1063
|
inputParams: [
|
|
1245
1064
|
{
|
|
1246
1065
|
name: "action",
|
|
1247
1066
|
type: "string",
|
|
1248
1067
|
required: true,
|
|
1249
|
-
description: "target action:
|
|
1068
|
+
description: "target action: workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard",
|
|
1250
1069
|
},
|
|
1251
1070
|
{
|
|
1252
1071
|
name: "actionParams",
|
|
@@ -1498,7 +1317,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1498
1317
|
"INVALID_INPUT",
|
|
1499
1318
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1500
1319
|
],
|
|
1501
|
-
description: "Full standalone task regenerate → validate → execute with parent lineage. Only when node rerun plan is ineligible or primaryRecovery recommends rerun-task; the server checks read-only run facts before accepting.
|
|
1320
|
+
description: "Full standalone task regenerate → validate → execute with parent lineage. Only when node rerun plan is ineligible or primaryRecovery recommends rerun-task; the server checks read-only run facts before accepting.",
|
|
1502
1321
|
inputParams: [
|
|
1503
1322
|
{
|
|
1504
1323
|
name: "runId",
|
|
@@ -1619,7 +1438,7 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1619
1438
|
{
|
|
1620
1439
|
command: "loop-agent task advance",
|
|
1621
1440
|
coverage: "model-callable",
|
|
1622
|
-
action: "
|
|
1441
|
+
action: "newTask",
|
|
1623
1442
|
source: "loop-agent",
|
|
1624
1443
|
},
|
|
1625
1444
|
{
|
|
@@ -2059,12 +1878,6 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
2059
1878
|
action: "dagReconcileRun",
|
|
2060
1879
|
source: "loop-agent",
|
|
2061
1880
|
},
|
|
2062
|
-
{
|
|
2063
|
-
command: "loop-agent dag request-interrupt",
|
|
2064
|
-
coverage: "human-gated-required",
|
|
2065
|
-
action: "dagRequestInterrupt",
|
|
2066
|
-
source: "loop-agent",
|
|
2067
|
-
},
|
|
2068
1881
|
{
|
|
2069
1882
|
command: "loop-agent dag rerun",
|
|
2070
1883
|
coverage: "model-callable",
|
|
@@ -45,48 +45,6 @@ export function readPackageName(packageDir) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Read the build stamp (git SHA + build time) written by the build pipeline
|
|
50
|
-
* into `<packageDir>/dist/build-stamp.json`. `undefined` when absent (e.g.
|
|
51
|
-
* running from source via tsx, or a hand-assembled package), which callers
|
|
52
|
-
* treat as "no stamp available".
|
|
53
|
-
*/
|
|
54
|
-
export function readBuildStamp(packageDir) {
|
|
55
|
-
try {
|
|
56
|
-
const raw = readFileSync(path.join(packageDir, "dist", "build-stamp.json"), "utf-8");
|
|
57
|
-
const parsed = JSON.parse(raw);
|
|
58
|
-
if (parsed.schemaVersion !== 1 ||
|
|
59
|
-
typeof parsed.version !== "string" ||
|
|
60
|
-
typeof parsed.builtAt !== "string")
|
|
61
|
-
return undefined;
|
|
62
|
-
return parsed;
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Human-readable CLI version label: plain version, enriched with the build
|
|
70
|
-
* stamp (short git SHA + build date) when available so a stale dist/ that was
|
|
71
|
-
* built from an old commit is immediately recognizable in `--version`.
|
|
72
|
-
*/
|
|
73
|
-
export function readCliVersionLabel(packageDir) {
|
|
74
|
-
const version = readPackageVersion(packageDir) ?? "0.0.0";
|
|
75
|
-
const stamp = readBuildStamp(packageDir);
|
|
76
|
-
if (!stamp?.gitSha)
|
|
77
|
-
return version;
|
|
78
|
-
return `${version} (build ${stamp.gitSha.slice(0, 8)} ${stamp.builtAt.slice(0, 10)})`;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Strip the build-stamp suffix from a CLI `--version` label, returning the bare
|
|
82
|
-
* SemVer. Controller-identity/preflight comparisons must use this rather than
|
|
83
|
-
* the raw label so a build-stamped `0.35.0 (build 7f8c5b5b …)` still matches
|
|
84
|
-
* `packageVersion === "0.35.0"`.
|
|
85
|
-
*/
|
|
86
|
-
export function normalizeCliVersionLabel(label) {
|
|
87
|
-
const match = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/.exec(label.trim());
|
|
88
|
-
return match ? match[0] : label.trim();
|
|
89
|
-
}
|
|
90
48
|
/**
|
|
91
49
|
* Compute the SHA-256 hash of a file on disk.
|
|
92
50
|
*
|
|
@@ -5,73 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
import { OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isOpenspecSpecFilePath, } from "../shared/openspec-spec.js";
|
|
10
|
-
const classifiedPathSchema = z
|
|
11
|
-
.string()
|
|
12
|
-
.refine((candidate) => isOpenspecSpecFilePath(candidate), "classified path must be a repo-relative supported spec file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
|
|
13
|
-
/**
|
|
14
|
-
* Stable semantic partition of `designEvidence.normativePaths`. Keys are a
|
|
15
|
-
* generic directory convention (not project-specific file names); the same
|
|
16
|
-
* repository therefore yields the same classification on repeated probes.
|
|
17
|
-
*/
|
|
18
|
-
export const frontendDesignClassifiedSchema = z
|
|
19
|
-
.object({
|
|
20
|
-
schemas: z.array(classifiedPathSchema),
|
|
21
|
-
codeTemplate: z.array(classifiedPathSchema),
|
|
22
|
-
rule: z
|
|
23
|
-
.object({
|
|
24
|
-
api: z.array(classifiedPathSchema),
|
|
25
|
-
mock: z.array(classifiedPathSchema),
|
|
26
|
-
router: z.array(classifiedPathSchema),
|
|
27
|
-
hooks: z.array(classifiedPathSchema),
|
|
28
|
-
utils: z.array(classifiedPathSchema),
|
|
29
|
-
components: z.array(classifiedPathSchema),
|
|
30
|
-
other: z.array(classifiedPathSchema),
|
|
31
|
-
})
|
|
32
|
-
.strict(),
|
|
33
|
-
theme: z.array(classifiedPathSchema),
|
|
34
|
-
component: z.array(classifiedPathSchema),
|
|
35
|
-
uiOther: z.array(classifiedPathSchema),
|
|
36
|
-
advisoryOther: z.array(classifiedPathSchema),
|
|
37
|
-
})
|
|
38
|
-
.strict();
|
|
39
|
-
export const frontendDesignEvidenceSchema = z
|
|
40
|
-
.object({
|
|
41
|
-
normativePaths: z.array(z.string()),
|
|
42
|
-
advisoryPaths: z.array(z.string()),
|
|
43
|
-
conflicts: z.array(z.string()),
|
|
44
|
-
classified: frontendDesignClassifiedSchema,
|
|
45
|
-
})
|
|
46
|
-
.strict()
|
|
47
|
-
.superRefine((evidence, ctx) => {
|
|
48
|
-
const classified = [
|
|
49
|
-
...evidence.classified.schemas,
|
|
50
|
-
...evidence.classified.codeTemplate,
|
|
51
|
-
...evidence.classified.rule.api,
|
|
52
|
-
...evidence.classified.rule.mock,
|
|
53
|
-
...evidence.classified.rule.router,
|
|
54
|
-
...evidence.classified.rule.hooks,
|
|
55
|
-
...evidence.classified.rule.utils,
|
|
56
|
-
...evidence.classified.rule.components,
|
|
57
|
-
...evidence.classified.rule.other,
|
|
58
|
-
...evidence.classified.theme,
|
|
59
|
-
...evidence.classified.component,
|
|
60
|
-
...evidence.classified.uiOther,
|
|
61
|
-
...evidence.classified.advisoryOther,
|
|
62
|
-
];
|
|
63
|
-
const classifiedSet = new Set(classified);
|
|
64
|
-
const normativeSet = new Set(evidence.normativePaths);
|
|
65
|
-
const onlyClassified = [...classifiedSet].filter((candidate) => !normativeSet.has(candidate));
|
|
66
|
-
const onlyNormative = [...normativeSet].filter((candidate) => !classifiedSet.has(candidate));
|
|
67
|
-
if (onlyClassified.length > 0 || onlyNormative.length > 0) {
|
|
68
|
-
ctx.addIssue({
|
|
69
|
-
code: z.ZodIssueCode.custom,
|
|
70
|
-
message: `classified must partition normativePaths exactly; extra in classified: ${onlyClassified.join(", ") || "(none)"}; missing from classified: ${onlyNormative.join(", ") || "(none)"}`,
|
|
71
|
-
path: ["classified"],
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
});
|
|
8
|
+
import { OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE } from "../shared/openspec-spec.js";
|
|
75
9
|
async function exists(filePath) {
|
|
76
10
|
try {
|
|
77
11
|
await stat(filePath);
|
|
@@ -107,14 +41,13 @@ async function listOpenspec(repoRoot) {
|
|
|
107
41
|
continue;
|
|
108
42
|
await walk(root, 0);
|
|
109
43
|
}
|
|
110
|
-
out.sort();
|
|
111
44
|
return out.slice(0, 40);
|
|
112
45
|
async function walk(dir, depth) {
|
|
113
46
|
if (depth > 4)
|
|
114
47
|
return;
|
|
115
48
|
let entries = [];
|
|
116
49
|
try {
|
|
117
|
-
entries =
|
|
50
|
+
entries = await readdir(dir);
|
|
118
51
|
}
|
|
119
52
|
catch {
|
|
120
53
|
return;
|
|
@@ -135,97 +68,6 @@ async function listOpenspec(repoRoot) {
|
|
|
135
68
|
}
|
|
136
69
|
}
|
|
137
70
|
}
|
|
138
|
-
function emptyClassified() {
|
|
139
|
-
return {
|
|
140
|
-
schemas: [],
|
|
141
|
-
codeTemplate: [],
|
|
142
|
-
rule: { api: [], mock: [], router: [], hooks: [], utils: [], components: [], other: [] },
|
|
143
|
-
theme: [],
|
|
144
|
-
component: [],
|
|
145
|
-
uiOther: [],
|
|
146
|
-
advisoryOther: [],
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Classify discovered openspec paths by generic directory convention. Only
|
|
151
|
-
* `isOpenspecSpecFilePath` paths enter a bucket; the classifier never invents
|
|
152
|
-
* project-specific file names. Basename stems use word-boundary matching so
|
|
153
|
-
* e.g. `*api*` (case-insensitive) maps to `rule.api` without hardcoding names.
|
|
154
|
-
*/
|
|
155
|
-
function classifyOpenspecPaths(paths) {
|
|
156
|
-
const classified = emptyClassified();
|
|
157
|
-
const ruleStem = (basename) => {
|
|
158
|
-
const lower = basename.toLowerCase();
|
|
159
|
-
const stems = [
|
|
160
|
-
["api", /\bapi\b/],
|
|
161
|
-
["mock", /\bmock\b/],
|
|
162
|
-
["router", /\brouter\b/],
|
|
163
|
-
["hooks", /\bhooks?\b/],
|
|
164
|
-
["utils", /\butils?\b/],
|
|
165
|
-
["components", /\bcomponents?\b/],
|
|
166
|
-
];
|
|
167
|
-
for (const [key, re] of stems) {
|
|
168
|
-
if (re.test(lower))
|
|
169
|
-
return key;
|
|
170
|
-
}
|
|
171
|
-
return "other";
|
|
172
|
-
};
|
|
173
|
-
for (const candidate of paths) {
|
|
174
|
-
if (!isOpenspecSpecFilePath(candidate))
|
|
175
|
-
continue;
|
|
176
|
-
const segments = candidate.split("/");
|
|
177
|
-
const basename = segments[segments.length - 1] ?? "";
|
|
178
|
-
if (candidate.startsWith("openspec/schemas/")) {
|
|
179
|
-
classified.schemas.push(candidate);
|
|
180
|
-
}
|
|
181
|
-
else if (candidate.startsWith("openspec/project-specs/templates/")) {
|
|
182
|
-
classified.codeTemplate.push(candidate);
|
|
183
|
-
}
|
|
184
|
-
else if (candidate.startsWith("openspec/project-specs/rules/")) {
|
|
185
|
-
classified.rule[ruleStem(basename)].push(candidate);
|
|
186
|
-
}
|
|
187
|
-
else if (candidate.startsWith("openspec/project-specs/ui/")) {
|
|
188
|
-
const lower = basename.toLowerCase();
|
|
189
|
-
if (/\btheme\b/.test(lower))
|
|
190
|
-
classified.theme.push(candidate);
|
|
191
|
-
else if (/\bcomponents?\b/.test(lower))
|
|
192
|
-
classified.component.push(candidate);
|
|
193
|
-
else
|
|
194
|
-
classified.uiOther.push(candidate);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
classified.advisoryOther.push(candidate);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const sortBucket = (bucket) => bucket.sort();
|
|
201
|
-
classified.schemas = sortBucket(classified.schemas);
|
|
202
|
-
classified.codeTemplate = sortBucket(classified.codeTemplate);
|
|
203
|
-
for (const key of [
|
|
204
|
-
"api",
|
|
205
|
-
"mock",
|
|
206
|
-
"router",
|
|
207
|
-
"hooks",
|
|
208
|
-
"utils",
|
|
209
|
-
"components",
|
|
210
|
-
"other",
|
|
211
|
-
]) {
|
|
212
|
-
classified.rule[key] = sortBucket(classified.rule[key]);
|
|
213
|
-
}
|
|
214
|
-
classified.theme = sortBucket(classified.theme);
|
|
215
|
-
classified.component = sortBucket(classified.component);
|
|
216
|
-
classified.uiOther = sortBucket(classified.uiOther);
|
|
217
|
-
classified.advisoryOther = sortBucket(classified.advisoryOther);
|
|
218
|
-
return classified;
|
|
219
|
-
}
|
|
220
|
-
function buildDesignEvidence(openspec) {
|
|
221
|
-
const normativePaths = [...new Set(openspec)].sort();
|
|
222
|
-
return frontendDesignEvidenceSchema.parse({
|
|
223
|
-
normativePaths,
|
|
224
|
-
advisoryPaths: [],
|
|
225
|
-
conflicts: [],
|
|
226
|
-
classified: classifyOpenspecPaths(normativePaths),
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
71
|
export function buildAdapterGuidance(capability) {
|
|
230
72
|
const lines = [
|
|
231
73
|
"## Frontend project capability (generation-time)",
|
|
@@ -279,7 +121,11 @@ export async function discoverFrontendProjectCapability(repoRoot) {
|
|
|
279
121
|
a11y: { status: "unknown", tools: [], evidencePaths: [] },
|
|
280
122
|
evidencePaths: openspec.slice(0, 5),
|
|
281
123
|
reasons: ["package.json missing or unreadable"],
|
|
282
|
-
designEvidence:
|
|
124
|
+
designEvidence: {
|
|
125
|
+
normativePaths: openspec,
|
|
126
|
+
advisoryPaths: [],
|
|
127
|
+
conflicts: [],
|
|
128
|
+
},
|
|
283
129
|
};
|
|
284
130
|
if (openspec.length) {
|
|
285
131
|
base.reasons.push(`openspec spec files discovered: ${openspec.slice(0, 5).join(", ")}`);
|
|
@@ -438,7 +284,11 @@ export async function discoverFrontendProjectCapability(repoRoot) {
|
|
|
438
284
|
if (hasDep(deps, "vue-router"))
|
|
439
285
|
router = "vue-router";
|
|
440
286
|
const openspec = await listOpenspec(repoRoot);
|
|
441
|
-
const designEvidence =
|
|
287
|
+
const designEvidence = {
|
|
288
|
+
normativePaths: openspec,
|
|
289
|
+
advisoryPaths: [],
|
|
290
|
+
conflicts: [],
|
|
291
|
+
};
|
|
442
292
|
if (openspec.length)
|
|
443
293
|
evidencePaths.push(...openspec.slice(0, 5));
|
|
444
294
|
const base = {
|
|
@@ -2,13 +2,6 @@ const FRESH_SOURCE_MISSING_CODES = new Set([
|
|
|
2
2
|
"MISSING_REQUIREMENT",
|
|
3
3
|
"MISSING_CONSTRAINTS",
|
|
4
4
|
]);
|
|
5
|
-
function hasKnowledgeSyncFeatureSurface(featureId, allowedPaths) {
|
|
6
|
-
const featureRoot = `features/${featureId}/`;
|
|
7
|
-
return allowedPaths.some((pathGlob) => {
|
|
8
|
-
const normalized = pathGlob.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
9
|
-
return normalized === `features/${featureId}` || normalized.startsWith(featureRoot);
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
5
|
export function isFreshLegacyUnversioned(state, sourceFilesPresent) {
|
|
13
6
|
if (state.effectiveStatus !== "legacy-unversioned")
|
|
14
7
|
return false;
|
|
@@ -112,16 +105,6 @@ export function listPrepareGaps(input) {
|
|
|
112
105
|
message: "featureId is required when taskKind is knowledge-sync",
|
|
113
106
|
});
|
|
114
107
|
}
|
|
115
|
-
if (draft.taskKind === "knowledge-sync" &&
|
|
116
|
-
draft.featureId &&
|
|
117
|
-
!hasKnowledgeSyncFeatureSurface(draft.featureId, draft.constraints.allowedPaths)) {
|
|
118
|
-
gaps.push({
|
|
119
|
-
code: "KNOWLEDGE_SYNC_PATH_MISMATCH",
|
|
120
|
-
level: "blocking",
|
|
121
|
-
field: "allowedPaths",
|
|
122
|
-
message: `knowledge-sync for ${draft.featureId} requires an allowed path under features/${draft.featureId}/; ordinary docs-only work should use taskKind standard`,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
108
|
if (draft.requirement.scope.length === 0) {
|
|
126
109
|
gaps.push({
|
|
127
110
|
code: "EMPTY_SCOPE",
|
|
@@ -15,21 +15,7 @@ const HEADING_ALIASES = {
|
|
|
15
15
|
"已确认需求",
|
|
16
16
|
]),
|
|
17
17
|
nonGoals: new Set(["非目标", "out of scope", "non-goals", "non goals"]),
|
|
18
|
-
acceptance: new Set([
|
|
19
|
-
"验收标准",
|
|
20
|
-
"acceptance criteria",
|
|
21
|
-
"acceptance",
|
|
22
|
-
// Common zh-CN product-writing variants seen in real PRDs; without
|
|
23
|
-
// these a PRD re-import flips to deterministic parse and loses all AC
|
|
24
|
-
// (EMPTY_ACCEPTANCE deadlock on the pendingPrdRefresh path).
|
|
25
|
-
"完成定义",
|
|
26
|
-
"完成标准",
|
|
27
|
-
"验收",
|
|
28
|
-
"验收条件",
|
|
29
|
-
"验收准则",
|
|
30
|
-
"dod",
|
|
31
|
-
"definition of done",
|
|
32
|
-
]),
|
|
18
|
+
acceptance: new Set(["验收标准", "acceptance criteria", "acceptance"]),
|
|
33
19
|
constraints: new Set(["约束", "constraints", "不变式", "invariants"]),
|
|
34
20
|
openQuestions: new Set(["待决问题", "open questions", "open-questions"]),
|
|
35
21
|
assumptions: new Set(["假设", "assumptions"]),
|