@tea-agent/loop-agent 0.33.7-beta.0 → 0.34.1
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 +67 -16
- package/dist/application/task-lifecycle/advance.js +309 -9
- package/dist/application/task-lifecycle/gates.js +50 -0
- package/dist/application/task-lifecycle/observe.js +11 -2
- package/dist/application/task-lifecycle/plan-transitions.js +13 -21
- package/dist/commands/init-upgrade.js +32 -1
- package/dist/commands/init.js +94 -3
- package/dist/executors/dag-pi-executor.js +18 -3
- package/dist/executors/shell-executor.js +4 -91
- package/dist/executors/shell-write-guard.js +26 -8
- package/dist/shared/operator/capabilities.js +98 -44
- package/dist/shared/resilient-git.js +133 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/index.js +2 -0
- package/dist/task/source-prepare/parse-intent.js +58 -10
- package/dist/task/source-prepare/prepare.js +229 -18
- package/dist/task/source-prepare/reference-integrity.js +18 -2
- package/dist/task/source-prepare/semantic-intake.js +404 -0
- package/dist/worker/console/app-data.js +2 -0
- package/dist/worker/console/chat/chat-event-store.js +190 -25
- package/dist/worker/console/chat/model-resolver.js +17 -0
- package/dist/worker/console/chat/pi-console-config.js +250 -32
- package/dist/worker/console/chat/pi-runtime.js +1007 -188
- package/dist/worker/console/chat/resource-loader.js +5 -4
- package/dist/worker/console/chat/routes.js +495 -157
- package/dist/worker/console/chat/runtime-context.js +48 -12
- package/dist/worker/console/chat/runtime-selection.js +59 -0
- package/dist/worker/console/chat/session-store.js +39 -0
- package/dist/worker/console/chat/shortcuts.js +1 -0
- package/dist/worker/console/chat/tool-adapter.js +9 -3
- package/dist/worker/console/chat/tools.js +5 -1
- package/dist/worker/console/dag-execution-receipt.js +380 -0
- package/dist/worker/console/interview/grill-me.js +7 -6
- package/dist/worker/console/operator-actions.js +785 -85
- package/dist/worker/console/prd-intake-bridge.js +393 -0
- package/dist/worker/console/recovery-cta.js +35 -6
- package/dist/worker/console/server.js +8 -15
- package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
- package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +1 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
- package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
- package/dist/worker/delivery/final-verification.js +13 -5
- package/dist/worker/delivery/package.js +31 -19
- package/dist/worker/delivery/verification-bundle.js +6 -4
- package/dist/worker/observability/read-model.js +3 -0
- package/dist/worker/observe/static/operator-chrome.css +5 -2
- package/dist/worker/observe/static/operator-chrome.js +6 -1
- package/dist/worker/observe/static/styles.css +39 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
- package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
- package/dist/workflows/dag/backend-test-module-stem.js +0 -5
- package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
- package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
- package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
- package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
- package/dist/workflows/dag/failure-category.js +3 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
- package/dist/workflows/dag/init-hybrid.js +49 -55
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/retry-policy.js +44 -11
- package/dist/workflows/dag/runner.js +6 -0
- package/dist/workflows/dag/scheduler.js +49 -1
- package/dist/workflows/dag/types.js +0 -7
- package/dist/workflows/dag/validate.js +16 -2
- package/dist/workflows/dag/workspace-checkpoint.js +8 -27
- package/docs/templates/agent-dag.schema.json +4 -4
- package/docs/templates/backend-test-dag.json +29 -32
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/references/command-reference.md +3 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
- package/skills/loop-agent/references/task-workflow.md +4 -0
- package/dist/worker/console/chat/instruction-skills.js +0 -217
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
- package/dist/worker/console/static/assets/index-CteJFFL2.js +0 -29
|
@@ -586,13 +586,13 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
586
586
|
},
|
|
587
587
|
{
|
|
588
588
|
action: "importPrd",
|
|
589
|
-
cli: "loop-agent task advance <id> --prd <server-staged-path> --json",
|
|
589
|
+
cli: "loop-agent task advance <id> --prd <server-staged-path> --json (+ intake bridge)",
|
|
590
590
|
kind: "mutation",
|
|
591
591
|
inputSchemaVersion: 1,
|
|
592
592
|
resultSchemaVersion: 1,
|
|
593
593
|
envelopeSchemaVersion: 1,
|
|
594
594
|
requiredErrorCodes: [...COMMON_MUTATION_ERRORS, "PATH_OUTSIDE_REPO"],
|
|
595
|
-
description: "Archive
|
|
595
|
+
description: "Archive PRD via task advance, run intake bridge (deterministic/semantic), rewrite Console draft.",
|
|
596
596
|
inputParams: [
|
|
597
597
|
{
|
|
598
598
|
name: "taskId",
|
|
@@ -606,6 +606,30 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
606
606
|
required: true,
|
|
607
607
|
description: "PRD markdown content",
|
|
608
608
|
},
|
|
609
|
+
{
|
|
610
|
+
name: "taskKind",
|
|
611
|
+
type: "string",
|
|
612
|
+
required: false,
|
|
613
|
+
description: "workflow kind (human-selected)",
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: "allowedPaths",
|
|
617
|
+
type: "array",
|
|
618
|
+
required: false,
|
|
619
|
+
description: "optional engineering allowedPaths (human-confirmed)",
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: "forbiddenPaths",
|
|
623
|
+
type: "array",
|
|
624
|
+
required: false,
|
|
625
|
+
description: "optional engineering forbiddenPaths",
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: "verifyCommands",
|
|
629
|
+
type: "array",
|
|
630
|
+
required: false,
|
|
631
|
+
description: "optional verify commands (label:command or shell)",
|
|
632
|
+
},
|
|
609
633
|
],
|
|
610
634
|
modelCallable: "always",
|
|
611
635
|
humanConfirmation: "none",
|
|
@@ -699,6 +723,36 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
699
723
|
modelCallable: "always",
|
|
700
724
|
humanConfirmation: "none",
|
|
701
725
|
},
|
|
726
|
+
{
|
|
727
|
+
action: "prepareDagExecution",
|
|
728
|
+
cli: "console aggregate execution receipt (server-side G2 assessment)",
|
|
729
|
+
kind: "read",
|
|
730
|
+
inputSchemaVersion: 1,
|
|
731
|
+
resultSchemaVersion: 1,
|
|
732
|
+
envelopeSchemaVersion: 1,
|
|
733
|
+
requiredErrorCodes: [
|
|
734
|
+
"INVALID_INPUT",
|
|
735
|
+
"BINDING_DRIFT",
|
|
736
|
+
"HUMAN_CONFIRMATION_REQUIRED",
|
|
737
|
+
],
|
|
738
|
+
description: "Issue a session-bound single-use execution receipt after server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
|
|
739
|
+
inputParams: [
|
|
740
|
+
{
|
|
741
|
+
name: "taskId",
|
|
742
|
+
type: "string",
|
|
743
|
+
required: true,
|
|
744
|
+
description: "task id",
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
name: "profile",
|
|
748
|
+
type: "string",
|
|
749
|
+
required: false,
|
|
750
|
+
description: "DAG profile (defaults to auto)",
|
|
751
|
+
},
|
|
752
|
+
],
|
|
753
|
+
modelCallable: "always",
|
|
754
|
+
humanConfirmation: "none",
|
|
755
|
+
},
|
|
702
756
|
{
|
|
703
757
|
action: "confirmDagConfirmation",
|
|
704
758
|
cli: "console aggregate confirmation human challenge (server-side)",
|
|
@@ -901,7 +955,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
901
955
|
},
|
|
902
956
|
{
|
|
903
957
|
action: "runDag",
|
|
904
|
-
cli: "loop-agent task advance <taskId> --approve-gate <id:digest> --json | dag execute --dag <path>",
|
|
958
|
+
cli: "loop-agent task advance <taskId> --approve-gate <id:digest> --json | dag execute --dag <staged-path>",
|
|
905
959
|
kind: "long-running",
|
|
906
960
|
inputSchemaVersion: 1,
|
|
907
961
|
resultSchemaVersion: 1,
|
|
@@ -911,19 +965,20 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
911
965
|
"CONTROLLER_MISMATCH",
|
|
912
966
|
"INVALID_INPUT",
|
|
913
967
|
],
|
|
914
|
-
description: "
|
|
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.",
|
|
915
969
|
inputParams: [
|
|
916
970
|
{
|
|
917
|
-
name: "
|
|
971
|
+
name: "executionId",
|
|
918
972
|
type: "string",
|
|
919
973
|
required: true,
|
|
920
|
-
description: "
|
|
974
|
+
description: "execution id (from prepareDagExecution) — raw dag path is never accepted from the model",
|
|
921
975
|
},
|
|
922
976
|
],
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
|
|
926
|
-
|
|
977
|
+
// always: the model can invoke runDag with a server-issued executionId.
|
|
978
|
+
// The server re-validates staged bytes/hash + bindings at consume time
|
|
979
|
+
// (2026-08-11 autonomous DAG supervision; receipt replaces browser gate).
|
|
980
|
+
modelCallable: "always",
|
|
981
|
+
humanConfirmation: "none",
|
|
927
982
|
},
|
|
928
983
|
{
|
|
929
984
|
action: "dagRerunPlan",
|
|
@@ -963,7 +1018,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
963
1018
|
"BINDING_DRIFT",
|
|
964
1019
|
"INVALID_INPUT",
|
|
965
1020
|
],
|
|
966
|
-
description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures.
|
|
1021
|
+
description: "Execute R1 continuation from effective node. Requires planHash from a fresh dagRerunPlan (eligible=true). Prefer this over new task / standaloneTaskRerun for provider flake and safe downstream failures. Server enforces planHash/binding/fingerprint freshness.",
|
|
967
1022
|
inputParams: [
|
|
968
1023
|
{
|
|
969
1024
|
name: "runId",
|
|
@@ -990,8 +1045,11 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
990
1045
|
description: "why continue from this node (e.g. provider flake; resume from review-pi)",
|
|
991
1046
|
},
|
|
992
1047
|
],
|
|
993
|
-
|
|
994
|
-
|
|
1048
|
+
// always: fresh eligible dagRerun plans are model-callable (2026-08-11).
|
|
1049
|
+
// The CLI keeps enforcing parent lifecycle, fromNode, planHash,
|
|
1050
|
+
// binding/fingerprint and request idempotency.
|
|
1051
|
+
modelCallable: "always",
|
|
1052
|
+
humanConfirmation: "none",
|
|
995
1053
|
},
|
|
996
1054
|
{
|
|
997
1055
|
action: "prepareMutationGate",
|
|
@@ -1001,13 +1059,13 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1001
1059
|
resultSchemaVersion: 1,
|
|
1002
1060
|
envelopeSchemaVersion: 1,
|
|
1003
1061
|
requiredErrorCodes: ["INVALID_INPUT", "NOT_FOUND"],
|
|
1004
|
-
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.",
|
|
1005
1063
|
inputParams: [
|
|
1006
1064
|
{
|
|
1007
1065
|
name: "action",
|
|
1008
1066
|
type: "string",
|
|
1009
1067
|
required: true,
|
|
1010
|
-
description: "target action:
|
|
1068
|
+
description: "target action: workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard",
|
|
1011
1069
|
},
|
|
1012
1070
|
{
|
|
1013
1071
|
name: "actionParams",
|
|
@@ -1259,7 +1317,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1259
1317
|
"INVALID_INPUT",
|
|
1260
1318
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1261
1319
|
],
|
|
1262
|
-
description: "Full standalone task regenerate → validate → execute with parent lineage.",
|
|
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.",
|
|
1263
1321
|
inputParams: [
|
|
1264
1322
|
{
|
|
1265
1323
|
name: "runId",
|
|
@@ -1274,20 +1332,22 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1274
1332
|
description: "rerun reason",
|
|
1275
1333
|
},
|
|
1276
1334
|
{
|
|
1277
|
-
name: "
|
|
1335
|
+
name: "profile",
|
|
1278
1336
|
type: "string",
|
|
1279
|
-
required:
|
|
1280
|
-
description: "
|
|
1337
|
+
required: false,
|
|
1338
|
+
description: "DAG profile (defaults to auto)",
|
|
1281
1339
|
},
|
|
1282
1340
|
{
|
|
1283
|
-
name: "
|
|
1284
|
-
type: "
|
|
1285
|
-
required:
|
|
1286
|
-
description: "
|
|
1341
|
+
name: "taskId",
|
|
1342
|
+
type: "string",
|
|
1343
|
+
required: false,
|
|
1344
|
+
description: "original task id (must match the run's task binding; never invent a new task-id)",
|
|
1287
1345
|
},
|
|
1288
1346
|
],
|
|
1289
|
-
|
|
1290
|
-
|
|
1347
|
+
// always: the server gate is the read-only run-facts check (2026-08-11);
|
|
1348
|
+
// no browser Human Gate token is required for safe same-task rerun.
|
|
1349
|
+
modelCallable: "always",
|
|
1350
|
+
humanConfirmation: "none",
|
|
1291
1351
|
},
|
|
1292
1352
|
{
|
|
1293
1353
|
action: "workerTaskRetry",
|
|
@@ -1301,7 +1361,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1301
1361
|
"INVALID_INPUT",
|
|
1302
1362
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
1303
1363
|
],
|
|
1304
|
-
description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store.",
|
|
1364
|
+
description: "Requeue failed Task Pool task (Failed → Ready) via in-package pool store. Only when read-only validation confirms the task exists in Failed state; the server checks this before accepting.",
|
|
1305
1365
|
inputParams: [
|
|
1306
1366
|
{
|
|
1307
1367
|
name: "taskId",
|
|
@@ -1321,21 +1381,11 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1321
1381
|
required: true,
|
|
1322
1382
|
description: "retry reason",
|
|
1323
1383
|
},
|
|
1324
|
-
{
|
|
1325
|
-
name: "confirmationId",
|
|
1326
|
-
type: "string",
|
|
1327
|
-
required: true,
|
|
1328
|
-
description: "mutation-gate receipt id from prepareMutationGate",
|
|
1329
|
-
},
|
|
1330
|
-
{
|
|
1331
|
-
name: "humanGateToken",
|
|
1332
|
-
type: "object",
|
|
1333
|
-
required: true,
|
|
1334
|
-
description: "server-signed human-gate token from prepareMutationGate",
|
|
1335
|
-
},
|
|
1336
1384
|
],
|
|
1337
|
-
|
|
1338
|
-
|
|
1385
|
+
// always: the server gate is the read-only worker task facts check
|
|
1386
|
+
// (2026-08-11); no browser Human Gate token is required.
|
|
1387
|
+
modelCallable: "always",
|
|
1388
|
+
humanConfirmation: "none",
|
|
1339
1389
|
},
|
|
1340
1390
|
...OFFICIAL_ACTIONS,
|
|
1341
1391
|
];
|
|
@@ -1411,9 +1461,10 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1411
1461
|
},
|
|
1412
1462
|
{
|
|
1413
1463
|
command: "loop-agent dag execute",
|
|
1414
|
-
coverage: "
|
|
1464
|
+
coverage: "model-callable",
|
|
1415
1465
|
action: "runDag",
|
|
1416
1466
|
source: "loop-agent",
|
|
1467
|
+
note: "runDag consumes a server-issued single-use execution receipt (prepareDagExecution); browser Human Gate no longer required for bounded DAGs (2026-08-11).",
|
|
1417
1468
|
},
|
|
1418
1469
|
{
|
|
1419
1470
|
command: "loop-agent cursor-prompt",
|
|
@@ -1829,15 +1880,17 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
1829
1880
|
},
|
|
1830
1881
|
{
|
|
1831
1882
|
command: "loop-agent dag rerun",
|
|
1832
|
-
coverage: "
|
|
1883
|
+
coverage: "model-callable",
|
|
1833
1884
|
action: "dagRerun",
|
|
1834
1885
|
source: "loop-agent",
|
|
1886
|
+
note: "fresh eligible dagRerunPlan (planHash) is model-callable; the CLI still enforces plan/binding/fingerprint freshness (2026-08-11).",
|
|
1835
1887
|
},
|
|
1836
1888
|
{
|
|
1837
1889
|
command: "loop-agent dag rerun-task",
|
|
1838
|
-
coverage: "
|
|
1890
|
+
coverage: "model-callable",
|
|
1839
1891
|
action: "standaloneTaskRerun",
|
|
1840
1892
|
source: "loop-agent",
|
|
1893
|
+
note: "server-side read-only run-facts check (ineligible plan / explicit rerun-task recommendation) replaces the browser Human Gate (2026-08-11).",
|
|
1841
1894
|
},
|
|
1842
1895
|
{
|
|
1843
1896
|
command: "loop-agent dag reconcile-tasks",
|
|
@@ -2069,9 +2122,10 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
2069
2122
|
},
|
|
2070
2123
|
{
|
|
2071
2124
|
command: "agent-worker task retry",
|
|
2072
|
-
coverage: "
|
|
2125
|
+
coverage: "model-callable",
|
|
2073
2126
|
action: "workerTaskRetry",
|
|
2074
2127
|
source: "agent-worker",
|
|
2128
|
+
note: "server-side read-only pool doctor facts check (Failed state) replaces the browser Human Gate (2026-08-11).",
|
|
2075
2129
|
},
|
|
2076
2130
|
{
|
|
2077
2131
|
command: "agent-worker task draft-followup",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access, constants } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** Thrown only for spawn/infrastructure failures or exhausted eligible startup retries. */
|
|
5
|
+
export class ResilientGitCommandError extends Error {
|
|
6
|
+
diagnostics;
|
|
7
|
+
constructor(input) {
|
|
8
|
+
const last = input.attempts.at(-1);
|
|
9
|
+
super(`git ${input.command} unavailable after ${input.attempts.length} attempts (exit code=${last?.exitCode ?? "unavailable"}, exit code hex=${last?.exitCodeHex ?? "unavailable"}, signal=${last?.signal ?? "unavailable"}, cwd=${path.resolve(input.cwd)}): ${last?.detail ?? "unknown infrastructure error"}`);
|
|
10
|
+
this.name = "ResilientGitCommandError";
|
|
11
|
+
this.diagnostics = { schemaVersion: 1, ...input };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function isResilientGitCommandError(error) {
|
|
15
|
+
return error instanceof ResilientGitCommandError;
|
|
16
|
+
}
|
|
17
|
+
export function formatWindowsExitCode(exitCode) {
|
|
18
|
+
return exitCode === undefined
|
|
19
|
+
? undefined
|
|
20
|
+
: `0x${(exitCode >>> 0).toString(16).padStart(8, "0").toUpperCase()}`;
|
|
21
|
+
}
|
|
22
|
+
export function isWindowsDllInitializationFailure(platform, exitCode) {
|
|
23
|
+
return platform === "win32" && exitCode !== undefined && (exitCode >>> 0) === 0xc0000142;
|
|
24
|
+
}
|
|
25
|
+
/** Only returns the original executable and its sibling within the same Git installation. */
|
|
26
|
+
export function deriveSameInstallationGitCandidates(primary) {
|
|
27
|
+
const normalized = path.win32.normalize(primary);
|
|
28
|
+
const lower = normalized.toLowerCase();
|
|
29
|
+
let sibling;
|
|
30
|
+
if (lower.endsWith("\\mingw64\\bin\\git.exe")) {
|
|
31
|
+
const root = path.win32.resolve(path.win32.dirname(normalized), "..", "..");
|
|
32
|
+
sibling = path.win32.join(root, "cmd", "git.exe");
|
|
33
|
+
}
|
|
34
|
+
else if (lower.endsWith("\\cmd\\git.exe")) {
|
|
35
|
+
const root = path.win32.resolve(path.win32.dirname(normalized), "..");
|
|
36
|
+
sibling = path.win32.join(root, "mingw64", "bin", "git.exe");
|
|
37
|
+
}
|
|
38
|
+
return [...new Set([normalized, ...(sibling ? [sibling] : [])])];
|
|
39
|
+
}
|
|
40
|
+
const windowsCandidateCache = new Map();
|
|
41
|
+
export async function resolveGitExecutableCandidates(platform, env) {
|
|
42
|
+
if (platform !== "win32")
|
|
43
|
+
return ["git"];
|
|
44
|
+
const pathValue = env.PATH ?? env.Path ?? "";
|
|
45
|
+
const cached = windowsCandidateCache.get(pathValue);
|
|
46
|
+
if (cached)
|
|
47
|
+
return cached;
|
|
48
|
+
const resolved = resolveWindowsGitExecutableCandidates(pathValue);
|
|
49
|
+
windowsCandidateCache.set(pathValue, resolved);
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
async function resolveWindowsGitExecutableCandidates(pathValue) {
|
|
53
|
+
for (const raw of pathValue.split(path.delimiter)) {
|
|
54
|
+
const entry = raw.trim().replace(/^"|"$/g, "");
|
|
55
|
+
if (!entry)
|
|
56
|
+
continue;
|
|
57
|
+
const candidate = path.win32.join(entry, "git.exe");
|
|
58
|
+
try {
|
|
59
|
+
await access(candidate, constants.X_OK);
|
|
60
|
+
const candidates = [];
|
|
61
|
+
for (const executable of deriveSameInstallationGitCandidates(candidate)) {
|
|
62
|
+
try {
|
|
63
|
+
await access(executable, constants.X_OK);
|
|
64
|
+
candidates.push(executable);
|
|
65
|
+
}
|
|
66
|
+
catch { /* optional sibling */ }
|
|
67
|
+
}
|
|
68
|
+
if (candidates.length)
|
|
69
|
+
return candidates;
|
|
70
|
+
}
|
|
71
|
+
catch { /* continue PATH search */ }
|
|
72
|
+
}
|
|
73
|
+
return ["git"];
|
|
74
|
+
}
|
|
75
|
+
const DEFAULT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
76
|
+
const bounded = (text) => text.length <= 1000 ? text : `${text.slice(0, 1000)}...[truncated]`;
|
|
77
|
+
/**
|
|
78
|
+
* Execute a read-only Git command with bounded recovery for Windows startup failures.
|
|
79
|
+
* Completed non-zero commands are deliberately returned to preserve caller semantics.
|
|
80
|
+
*/
|
|
81
|
+
export async function runResilientGitCommand(options, dependencies = {}) {
|
|
82
|
+
const platform = dependencies.platform ?? process.platform;
|
|
83
|
+
const env = { ...(dependencies.env ?? process.env), GIT_OPTIONAL_LOCKS: "0" };
|
|
84
|
+
const candidates = await (dependencies.resolveExecutableCandidates ?? resolveGitExecutableCandidates)(platform, env);
|
|
85
|
+
const executables = candidates.length ? candidates : ["git"];
|
|
86
|
+
const maxAttempts = Math.max(1, options.attempts ?? 3);
|
|
87
|
+
const delay = Math.max(0, options.retryDelayMs ?? 250);
|
|
88
|
+
const execute = dependencies.runAttempt ?? spawnGitAttempt;
|
|
89
|
+
const sleep = dependencies.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
90
|
+
const now = dependencies.now ?? Date.now;
|
|
91
|
+
const attemptDiagnostics = [];
|
|
92
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
93
|
+
const executable = executables[(attempt - 1) % executables.length];
|
|
94
|
+
const startedAt = now();
|
|
95
|
+
try {
|
|
96
|
+
const result = await execute({ executable, args: options.args, cwd: options.cwd, env, windowsHide: true, maxBuffer: options.maxBuffer ?? DEFAULT_MAX_BUFFER });
|
|
97
|
+
if (result.code === 0)
|
|
98
|
+
return result;
|
|
99
|
+
const dllFailure = isWindowsDllInitializationFailure(platform, result.code);
|
|
100
|
+
const emptyStartupFailure = result.stdout.length === 0 && result.stderr.length === 0;
|
|
101
|
+
const eligible = platform === "win32" && (dllFailure || emptyStartupFailure);
|
|
102
|
+
if (!eligible)
|
|
103
|
+
return result;
|
|
104
|
+
attemptDiagnostics.push({
|
|
105
|
+
attempt, executable, exitCode: result.code, ...(formatWindowsExitCode(result.code) ? { exitCodeHex: formatWindowsExitCode(result.code) } : {}), signal: result.signal,
|
|
106
|
+
durationMs: Math.max(0, now() - startedAt), detail: bounded("no stdout/stderr"),
|
|
107
|
+
transientKind: dllFailure ? "windows-dll-init-failed" : "empty-output-startup-failure",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const detail = bounded(error instanceof Error ? error.message : String(error));
|
|
112
|
+
attemptDiagnostics.push({ attempt, executable, signal: null, durationMs: Math.max(0, now() - startedAt), detail });
|
|
113
|
+
}
|
|
114
|
+
if (attempt < maxAttempts && delay > 0)
|
|
115
|
+
await sleep(Math.min(4_000, delay * 2 ** (attempt - 1)));
|
|
116
|
+
}
|
|
117
|
+
throw new ResilientGitCommandError({ cwd: options.cwd, command: options.args.join(" "), platform, executableCandidates: executables, attempts: attemptDiagnostics });
|
|
118
|
+
}
|
|
119
|
+
function spawnGitAttempt(input) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const child = spawn(input.executable, input.args, { cwd: input.cwd, env: input.env, windowsHide: input.windowsHide, stdio: ["ignore", "pipe", "pipe"] });
|
|
122
|
+
let stdout = "";
|
|
123
|
+
let stderr = "";
|
|
124
|
+
const append = (current, chunk) => {
|
|
125
|
+
const next = current + String(chunk);
|
|
126
|
+
return next.length > input.maxBuffer ? next.slice(0, input.maxBuffer) : next;
|
|
127
|
+
};
|
|
128
|
+
child.stdout.on("data", (chunk) => { stdout = append(stdout, chunk); });
|
|
129
|
+
child.stderr.on("data", (chunk) => { stderr = append(stderr, chunk); });
|
|
130
|
+
child.on("error", reject);
|
|
131
|
+
child.on("close", (code, signal) => resolve({ code: code ?? 1, signal, stdout, stderr }));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product Analysis / Product Requirement artifact frontmatter detection.
|
|
3
|
+
* Deterministic; no LLM. Used to route intake errors and parseable roles.
|
|
4
|
+
*/
|
|
5
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u;
|
|
6
|
+
function parseYamlishScalar(raw) {
|
|
7
|
+
const trimmed = raw.trim();
|
|
8
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
9
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
10
|
+
return trimmed.slice(1, -1).trim();
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
function parseSimpleFrontmatter(block) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const line of block.split(/\r?\n/)) {
|
|
17
|
+
const match = /^([A-Za-z0-9_-]+)\s*:\s*(.+?)\s*$/u.exec(line);
|
|
18
|
+
if (!match)
|
|
19
|
+
continue;
|
|
20
|
+
out[match[1]] = parseYamlishScalar(match[2]);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function normalizeArtifactType(raw) {
|
|
25
|
+
if (!raw)
|
|
26
|
+
return "unknown";
|
|
27
|
+
const value = raw.trim().toLowerCase();
|
|
28
|
+
if (value === "product-requirement" || value === "product_requirement") {
|
|
29
|
+
return "product-requirement";
|
|
30
|
+
}
|
|
31
|
+
if (value === "product-analysis" || value === "product_analysis") {
|
|
32
|
+
return "product-analysis";
|
|
33
|
+
}
|
|
34
|
+
if (value === "requirement-clarification" ||
|
|
35
|
+
value === "requirement_clarification" ||
|
|
36
|
+
value === "product-clarification") {
|
|
37
|
+
return "requirement-clarification";
|
|
38
|
+
}
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detect Product Analysis V3/V4 style artifact metadata from markdown body.
|
|
43
|
+
*/
|
|
44
|
+
export function detectProductArtifactMeta(markdown) {
|
|
45
|
+
const match = FRONTMATTER_RE.exec(markdown);
|
|
46
|
+
if (!match) {
|
|
47
|
+
return { artifactType: "unknown", recognized: false };
|
|
48
|
+
}
|
|
49
|
+
const fields = parseSimpleFrontmatter(match[1]);
|
|
50
|
+
const artifactType = normalizeArtifactType(fields.artifact_type);
|
|
51
|
+
const recognized = artifactType !== "unknown" || Boolean(fields.artifact_version);
|
|
52
|
+
return {
|
|
53
|
+
artifactType,
|
|
54
|
+
...(fields.artifact_version
|
|
55
|
+
? { artifactVersion: fields.artifact_version }
|
|
56
|
+
: {}),
|
|
57
|
+
...(fields.requirement_status
|
|
58
|
+
? { requirementStatus: fields.requirement_status }
|
|
59
|
+
: {}),
|
|
60
|
+
...(fields.analysis_status
|
|
61
|
+
? { analysisStatus: fields.analysis_status }
|
|
62
|
+
: {}),
|
|
63
|
+
...(fields.requirement_id ? { requirementId: fields.requirement_id } : {}),
|
|
64
|
+
...(fields.analysis_scope ? { analysisScope: fields.analysis_scope } : {}),
|
|
65
|
+
recognized,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Infer import role from file path basename when --role is omitted.
|
|
70
|
+
*/
|
|
71
|
+
export function inferPrdImportRoleFromPath(filePath) {
|
|
72
|
+
const base = filePath
|
|
73
|
+
.replace(/\\/g, "/")
|
|
74
|
+
.split("/")
|
|
75
|
+
.pop()
|
|
76
|
+
?.toLowerCase()
|
|
77
|
+
.replace(/\.(md|markdown|txt)$/u, "") ?? "";
|
|
78
|
+
if (base === "product-analysis" ||
|
|
79
|
+
base.endsWith("-product-analysis") ||
|
|
80
|
+
base.includes("product-analysis")) {
|
|
81
|
+
return "analysis";
|
|
82
|
+
}
|
|
83
|
+
if (base === "requirement-clarification" ||
|
|
84
|
+
base.endsWith("-requirement-clarification") ||
|
|
85
|
+
base.includes("requirement-clarification") ||
|
|
86
|
+
base.includes("clarification")) {
|
|
87
|
+
return "clarification";
|
|
88
|
+
}
|
|
89
|
+
if (base === "product-requirement" ||
|
|
90
|
+
base.endsWith("-product-requirement") ||
|
|
91
|
+
base.includes("product-requirement")) {
|
|
92
|
+
return "requirement";
|
|
93
|
+
}
|
|
94
|
+
if (base.includes("acceptance"))
|
|
95
|
+
return "acceptance";
|
|
96
|
+
return "requirement";
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Prefer path inference; if markdown frontmatter is available and more specific, use it.
|
|
100
|
+
*/
|
|
101
|
+
export function resolvePrdImportRole(input) {
|
|
102
|
+
if (input.explicitRole?.trim())
|
|
103
|
+
return input.explicitRole.trim();
|
|
104
|
+
if (input.markdown) {
|
|
105
|
+
const meta = detectProductArtifactMeta(input.markdown);
|
|
106
|
+
if (meta.artifactType === "product-analysis")
|
|
107
|
+
return "analysis";
|
|
108
|
+
if (meta.artifactType === "requirement-clarification") {
|
|
109
|
+
return "clarification";
|
|
110
|
+
}
|
|
111
|
+
if (meta.artifactType === "product-requirement")
|
|
112
|
+
return "requirement";
|
|
113
|
+
}
|
|
114
|
+
return inferPrdImportRoleFromPath(input.filePath);
|
|
115
|
+
}
|
|
116
|
+
/** Roles that may contribute requirement facts (objective/AC/scope). */
|
|
117
|
+
export const REQUIREMENT_FACT_ROLES = new Set(["requirement", "acceptance"]);
|
|
118
|
+
/** Roles that are archival references only (not fact sources). */
|
|
119
|
+
export const REFERENCE_ONLY_ROLES = new Set(["analysis", "clarification", "design"]);
|
|
120
|
+
export function isIntakeSoftGapCode(code) {
|
|
121
|
+
return (code === "EMPTY_ACCEPTANCE" ||
|
|
122
|
+
code === "EMPTY_OBJECTIVE" ||
|
|
123
|
+
code === "EMPTY_ALLOWED_PATHS" ||
|
|
124
|
+
code === "EMPTY_TASK_KIND" ||
|
|
125
|
+
code === "NO_PARSEABLE_REQUIREMENT" ||
|
|
126
|
+
code === "PRODUCT_ANALYSIS_NOT_EXECUTABLE" ||
|
|
127
|
+
code === "PRODUCT_REQUIREMENT_PENDING" ||
|
|
128
|
+
code === "PRODUCT_CLARIFICATION_NOT_EXECUTABLE" ||
|
|
129
|
+
code === "PRODUCT_ARTIFACT_NOT_EXECUTABLE" ||
|
|
130
|
+
code === "MISSING_FEATURE_ID" ||
|
|
131
|
+
code === "SEMANTIC_INTAKE_RECOMMENDED" ||
|
|
132
|
+
code === "SEMANTIC_INTAKE_FAILED" ||
|
|
133
|
+
code === "SEMANTIC_INTAKE_INVALID_OUTPUT" ||
|
|
134
|
+
code === "SEMANTIC_INTAKE_PI_FAILED" ||
|
|
135
|
+
code === "SEMANTIC_INTAKE_NO_DOCUMENTS" ||
|
|
136
|
+
code === "SEMANTIC_INTAKE_REFUSED_NON_EXECUTABLE");
|
|
137
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
2
|
export * from "./path-policy.js";
|
|
3
3
|
export * from "./parse-intent.js";
|
|
4
|
+
export * from "./artifact-meta.js";
|
|
5
|
+
export * from "./semantic-intake.js";
|
|
4
6
|
export * from "./reference-integrity.js";
|
|
5
7
|
export * from "./completeness.js";
|
|
6
8
|
export * from "./build-draft.js";
|