@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.
Files changed (101) hide show
  1. package/CHANGELOG.md +67 -16
  2. package/dist/application/task-lifecycle/advance.js +309 -9
  3. package/dist/application/task-lifecycle/gates.js +50 -0
  4. package/dist/application/task-lifecycle/observe.js +11 -2
  5. package/dist/application/task-lifecycle/plan-transitions.js +13 -21
  6. package/dist/commands/init-upgrade.js +32 -1
  7. package/dist/commands/init.js +94 -3
  8. package/dist/executors/dag-pi-executor.js +18 -3
  9. package/dist/executors/shell-executor.js +4 -91
  10. package/dist/executors/shell-write-guard.js +26 -8
  11. package/dist/shared/operator/capabilities.js +98 -44
  12. package/dist/shared/resilient-git.js +133 -0
  13. package/dist/task/source-prepare/artifact-meta.js +137 -0
  14. package/dist/task/source-prepare/index.js +2 -0
  15. package/dist/task/source-prepare/parse-intent.js +58 -10
  16. package/dist/task/source-prepare/prepare.js +229 -18
  17. package/dist/task/source-prepare/reference-integrity.js +18 -2
  18. package/dist/task/source-prepare/semantic-intake.js +404 -0
  19. package/dist/worker/console/app-data.js +2 -0
  20. package/dist/worker/console/chat/chat-event-store.js +190 -25
  21. package/dist/worker/console/chat/model-resolver.js +17 -0
  22. package/dist/worker/console/chat/pi-console-config.js +250 -32
  23. package/dist/worker/console/chat/pi-runtime.js +1007 -188
  24. package/dist/worker/console/chat/resource-loader.js +5 -4
  25. package/dist/worker/console/chat/routes.js +495 -157
  26. package/dist/worker/console/chat/runtime-context.js +48 -12
  27. package/dist/worker/console/chat/runtime-selection.js +59 -0
  28. package/dist/worker/console/chat/session-store.js +39 -0
  29. package/dist/worker/console/chat/shortcuts.js +1 -0
  30. package/dist/worker/console/chat/tool-adapter.js +9 -3
  31. package/dist/worker/console/chat/tools.js +5 -1
  32. package/dist/worker/console/dag-execution-receipt.js +380 -0
  33. package/dist/worker/console/interview/grill-me.js +7 -6
  34. package/dist/worker/console/operator-actions.js +785 -85
  35. package/dist/worker/console/prd-intake-bridge.js +393 -0
  36. package/dist/worker/console/recovery-cta.js +35 -6
  37. package/dist/worker/console/server.js +8 -15
  38. package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
  39. package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +1 -0
  42. package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
  45. package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
  46. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
  47. package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
  48. package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
  49. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
  50. package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
  51. package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
  52. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
  53. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
  54. package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
  55. package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
  56. package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
  57. package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
  58. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
  59. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
  60. package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
  61. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
  62. package/dist/worker/delivery/final-verification.js +13 -5
  63. package/dist/worker/delivery/package.js +31 -19
  64. package/dist/worker/delivery/verification-bundle.js +6 -4
  65. package/dist/worker/observability/read-model.js +3 -0
  66. package/dist/worker/observe/static/operator-chrome.css +5 -2
  67. package/dist/worker/observe/static/operator-chrome.js +6 -1
  68. package/dist/worker/observe/static/styles.css +39 -9
  69. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
  70. package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
  71. package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
  72. package/dist/workflows/dag/backend-test-module-stem.js +0 -5
  73. package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
  74. package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
  75. package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
  76. package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
  77. package/dist/workflows/dag/failure-category.js +3 -0
  78. package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
  79. package/dist/workflows/dag/init-hybrid.js +49 -55
  80. package/dist/workflows/dag/node-execution.js +3 -2
  81. package/dist/workflows/dag/retry-policy.js +44 -11
  82. package/dist/workflows/dag/runner.js +6 -0
  83. package/dist/workflows/dag/scheduler.js +49 -1
  84. package/dist/workflows/dag/types.js +0 -7
  85. package/dist/workflows/dag/validate.js +16 -2
  86. package/dist/workflows/dag/workspace-checkpoint.js +8 -27
  87. package/docs/templates/agent-dag.schema.json +4 -4
  88. package/docs/templates/backend-test-dag.json +29 -32
  89. package/harness.json +1 -1
  90. package/package.json +1 -1
  91. package/skills/local-jacoco-coverage/SKILL.md +281 -0
  92. package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
  93. package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
  94. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
  95. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
  96. package/skills/loop-agent/references/command-reference.md +3 -1
  97. package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
  98. package/skills/loop-agent/references/task-workflow.md +4 -0
  99. package/dist/worker/console/chat/instruction-skills.js +0 -217
  100. package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
  101. 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 immutable PRD via task advance (server-staged file).",
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: "Execute reviewed DAG after human confirmation (prefer task advance --approve-gate when gate token present).",
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: "confirmationId",
971
+ name: "executionId",
918
972
  type: "string",
919
973
  required: true,
920
- description: "confirmation id (from prepareDagConfirmation) — raw dag path is never accepted from the model",
974
+ description: "execution id (from prepareDagExecution) — raw dag path is never accepted from the model",
921
975
  },
922
976
  ],
923
- // prepare-only: the model can invoke runDag but it MUST carry a
924
- // confirmationId that was consumed by a human-origin confirm (M0-B).
925
- modelCallable: "prepare-only",
926
- humanConfirmation: "required",
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. Human Gate required.",
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
- modelCallable: "prepare-only",
994
- humanConfirmation: "required",
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 standaloneTaskRerun / workerTaskRetry / Night Scheduler mutations. Model may prepare; only the browser mutation gate may consume.",
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: standaloneTaskRerun | workerTaskRetry | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard",
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: "confirmationId",
1335
+ name: "profile",
1278
1336
  type: "string",
1279
- required: true,
1280
- description: "mutation-gate receipt id from prepareMutationGate",
1337
+ required: false,
1338
+ description: "DAG profile (defaults to auto)",
1281
1339
  },
1282
1340
  {
1283
- name: "humanGateToken",
1284
- type: "object",
1285
- required: true,
1286
- description: "server-signed human-gate token from prepareMutationGate",
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
- modelCallable: "prepare-only",
1290
- humanConfirmation: "required",
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
- modelCallable: "prepare-only",
1338
- humanConfirmation: "required",
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: "human-gated-required",
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: "human-gated-required",
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: "human-gated-required",
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: "human-gated-required",
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";