@moreih29/nexus-core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +76 -57
  2. package/assets/agents/architect/body.ko.md +177 -0
  3. package/{agents → assets/agents}/architect/body.md +16 -0
  4. package/assets/agents/designer/body.ko.md +125 -0
  5. package/{agents → assets/agents}/designer/body.md +16 -0
  6. package/assets/agents/engineer/body.ko.md +106 -0
  7. package/{agents → assets/agents}/engineer/body.md +14 -0
  8. package/assets/agents/lead/body.ko.md +70 -0
  9. package/assets/agents/lead/body.md +70 -0
  10. package/assets/agents/postdoc/body.ko.md +122 -0
  11. package/{agents → assets/agents}/postdoc/body.md +16 -0
  12. package/assets/agents/researcher/body.ko.md +137 -0
  13. package/{agents → assets/agents}/researcher/body.md +15 -0
  14. package/assets/agents/reviewer/body.ko.md +138 -0
  15. package/{agents → assets/agents}/reviewer/body.md +15 -0
  16. package/assets/agents/strategist/body.ko.md +116 -0
  17. package/{agents → assets/agents}/strategist/body.md +16 -0
  18. package/assets/agents/tester/body.ko.md +195 -0
  19. package/{agents → assets/agents}/tester/body.md +15 -0
  20. package/assets/agents/writer/body.ko.md +122 -0
  21. package/{agents → assets/agents}/writer/body.md +14 -0
  22. package/assets/capability-matrix.yml +198 -0
  23. package/assets/hooks/agent-bootstrap/handler.test.ts +368 -0
  24. package/assets/hooks/agent-bootstrap/handler.ts +119 -0
  25. package/assets/hooks/agent-bootstrap/meta.yml +10 -0
  26. package/assets/hooks/agent-finalize/handler.test.ts +368 -0
  27. package/assets/hooks/agent-finalize/handler.ts +76 -0
  28. package/assets/hooks/agent-finalize/meta.yml +10 -0
  29. package/assets/hooks/capability-matrix.yml +313 -0
  30. package/assets/hooks/post-tool-telemetry/handler.test.ts +302 -0
  31. package/assets/hooks/post-tool-telemetry/handler.ts +49 -0
  32. package/assets/hooks/post-tool-telemetry/meta.yml +11 -0
  33. package/assets/hooks/prompt-router/handler.test.ts +801 -0
  34. package/assets/hooks/prompt-router/handler.ts +261 -0
  35. package/assets/hooks/prompt-router/meta.yml +11 -0
  36. package/assets/hooks/session-init/handler.test.ts +274 -0
  37. package/assets/hooks/session-init/handler.ts +30 -0
  38. package/assets/hooks/session-init/meta.yml +9 -0
  39. package/assets/lsp-servers.json +55 -0
  40. package/assets/schema/lsp-servers.schema.json +67 -0
  41. package/assets/skills/nx-init/body.ko.md +197 -0
  42. package/{skills → assets/skills}/nx-init/body.md +11 -0
  43. package/assets/skills/nx-plan/body.ko.md +361 -0
  44. package/{skills → assets/skills}/nx-plan/body.md +13 -0
  45. package/assets/skills/nx-run/body.ko.md +161 -0
  46. package/{skills → assets/skills}/nx-run/body.md +11 -0
  47. package/assets/skills/nx-sync/body.ko.md +92 -0
  48. package/{skills → assets/skills}/nx-sync/body.md +10 -0
  49. package/assets/tools/tool-name-map.yml +353 -0
  50. package/dist/assets/hooks/agent-bootstrap/handler.d.ts +4 -0
  51. package/dist/assets/hooks/agent-bootstrap/handler.d.ts.map +1 -0
  52. package/dist/assets/hooks/agent-bootstrap/handler.js +100 -0
  53. package/dist/assets/hooks/agent-bootstrap/handler.js.map +1 -0
  54. package/dist/assets/hooks/agent-finalize/handler.d.ts +4 -0
  55. package/dist/assets/hooks/agent-finalize/handler.d.ts.map +1 -0
  56. package/dist/assets/hooks/agent-finalize/handler.js +63 -0
  57. package/dist/assets/hooks/agent-finalize/handler.js.map +1 -0
  58. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts +4 -0
  59. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts.map +1 -0
  60. package/dist/assets/hooks/post-tool-telemetry/handler.js +40 -0
  61. package/dist/assets/hooks/post-tool-telemetry/handler.js.map +1 -0
  62. package/dist/assets/hooks/prompt-router/handler.d.ts +4 -0
  63. package/dist/assets/hooks/prompt-router/handler.d.ts.map +1 -0
  64. package/dist/assets/hooks/prompt-router/handler.js +204 -0
  65. package/dist/assets/hooks/prompt-router/handler.js.map +1 -0
  66. package/dist/assets/hooks/session-init/handler.d.ts +4 -0
  67. package/dist/assets/hooks/session-init/handler.d.ts.map +1 -0
  68. package/dist/assets/hooks/session-init/handler.js +23 -0
  69. package/dist/assets/hooks/session-init/handler.js.map +1 -0
  70. package/dist/hooks/agent-bootstrap.js +105 -0
  71. package/dist/hooks/agent-finalize.js +164 -0
  72. package/dist/hooks/post-tool-telemetry.js +55 -0
  73. package/dist/hooks/prompt-router.js +7300 -0
  74. package/dist/hooks/session-init.js +21 -0
  75. package/dist/manifests/claude-hooks.json +52 -0
  76. package/dist/manifests/codex-hooks.json +28 -0
  77. package/dist/manifests/opencode-manifest.json +44 -0
  78. package/dist/manifests/portability-report.json +87 -0
  79. package/dist/scripts/build-agents.d.ts +157 -0
  80. package/dist/scripts/build-agents.d.ts.map +1 -0
  81. package/dist/scripts/build-agents.js +737 -0
  82. package/dist/scripts/build-agents.js.map +1 -0
  83. package/dist/scripts/build-hooks.d.ts +16 -0
  84. package/dist/scripts/build-hooks.d.ts.map +1 -0
  85. package/dist/scripts/build-hooks.js +388 -0
  86. package/dist/scripts/build-hooks.js.map +1 -0
  87. package/dist/scripts/cli.d.ts +54 -0
  88. package/dist/scripts/cli.d.ts.map +1 -0
  89. package/dist/scripts/cli.js +467 -0
  90. package/dist/scripts/cli.js.map +1 -0
  91. package/dist/src/hooks/opencode-mount.d.ts +35 -0
  92. package/dist/src/hooks/opencode-mount.d.ts.map +1 -0
  93. package/dist/src/hooks/opencode-mount.js +352 -0
  94. package/dist/src/hooks/opencode-mount.js.map +1 -0
  95. package/dist/src/hooks/runtime.d.ts +37 -0
  96. package/dist/src/hooks/runtime.d.ts.map +1 -0
  97. package/dist/src/hooks/runtime.js +274 -0
  98. package/dist/src/hooks/runtime.js.map +1 -0
  99. package/dist/src/hooks/types.d.ts +196 -0
  100. package/dist/src/hooks/types.d.ts.map +1 -0
  101. package/dist/src/hooks/types.js +85 -0
  102. package/dist/src/hooks/types.js.map +1 -0
  103. package/dist/src/lsp/cache.d.ts +9 -0
  104. package/dist/src/lsp/cache.d.ts.map +1 -0
  105. package/dist/src/lsp/cache.js +216 -0
  106. package/dist/src/lsp/cache.js.map +1 -0
  107. package/dist/src/lsp/client.d.ts +24 -0
  108. package/dist/src/lsp/client.d.ts.map +1 -0
  109. package/dist/src/lsp/client.js +166 -0
  110. package/dist/src/lsp/client.js.map +1 -0
  111. package/dist/src/lsp/detect.d.ts +77 -0
  112. package/dist/src/lsp/detect.d.ts.map +1 -0
  113. package/dist/src/lsp/detect.js +116 -0
  114. package/dist/src/lsp/detect.js.map +1 -0
  115. package/dist/src/mcp/server.d.ts +5 -0
  116. package/dist/src/mcp/server.d.ts.map +1 -0
  117. package/dist/src/mcp/server.js +34 -0
  118. package/dist/src/mcp/server.js.map +1 -0
  119. package/dist/src/mcp/tools/artifact.d.ts +4 -0
  120. package/dist/src/mcp/tools/artifact.d.ts.map +1 -0
  121. package/dist/src/mcp/tools/artifact.js +36 -0
  122. package/dist/src/mcp/tools/artifact.js.map +1 -0
  123. package/dist/src/mcp/tools/history.d.ts +3 -0
  124. package/dist/src/mcp/tools/history.d.ts.map +1 -0
  125. package/dist/src/mcp/tools/history.js +29 -0
  126. package/dist/src/mcp/tools/history.js.map +1 -0
  127. package/dist/src/mcp/tools/lsp.d.ts +13 -0
  128. package/dist/src/mcp/tools/lsp.d.ts.map +1 -0
  129. package/dist/src/mcp/tools/lsp.js +225 -0
  130. package/dist/src/mcp/tools/lsp.js.map +1 -0
  131. package/dist/src/mcp/tools/plan.d.ts +3 -0
  132. package/dist/src/mcp/tools/plan.d.ts.map +1 -0
  133. package/dist/src/mcp/tools/plan.js +317 -0
  134. package/dist/src/mcp/tools/plan.js.map +1 -0
  135. package/dist/src/mcp/tools/task.d.ts +3 -0
  136. package/dist/src/mcp/tools/task.d.ts.map +1 -0
  137. package/dist/src/mcp/tools/task.js +252 -0
  138. package/dist/src/mcp/tools/task.js.map +1 -0
  139. package/dist/src/shared/invocations.d.ts +74 -0
  140. package/dist/src/shared/invocations.d.ts.map +1 -0
  141. package/dist/src/shared/invocations.js +247 -0
  142. package/dist/src/shared/invocations.js.map +1 -0
  143. package/dist/src/shared/json-store.d.ts +37 -0
  144. package/dist/src/shared/json-store.d.ts.map +1 -0
  145. package/dist/src/shared/json-store.js +163 -0
  146. package/dist/src/shared/json-store.js.map +1 -0
  147. package/dist/src/shared/mcp-utils.d.ts +3 -0
  148. package/dist/src/shared/mcp-utils.d.ts.map +1 -0
  149. package/dist/src/shared/mcp-utils.js +6 -0
  150. package/dist/src/shared/mcp-utils.js.map +1 -0
  151. package/dist/src/shared/paths.d.ts +21 -0
  152. package/dist/src/shared/paths.d.ts.map +1 -0
  153. package/dist/src/shared/paths.js +81 -0
  154. package/dist/src/shared/paths.js.map +1 -0
  155. package/dist/src/shared/tool-log.d.ts +8 -0
  156. package/dist/src/shared/tool-log.d.ts.map +1 -0
  157. package/dist/src/shared/tool-log.js +22 -0
  158. package/dist/src/shared/tool-log.js.map +1 -0
  159. package/dist/src/types/state.d.ts +862 -0
  160. package/dist/src/types/state.d.ts.map +1 -0
  161. package/dist/src/types/state.js +66 -0
  162. package/dist/src/types/state.js.map +1 -0
  163. package/docs/consuming/codex-lead-merge.md +106 -0
  164. package/docs/plugin-guide.md +396 -0
  165. package/docs/plugin-template/claude/.github/workflows/build.yml +60 -0
  166. package/docs/plugin-template/claude/README.md +110 -0
  167. package/docs/plugin-template/claude/package.json +16 -0
  168. package/docs/plugin-template/codex/.github/workflows/build.yml +51 -0
  169. package/docs/plugin-template/codex/README.md +147 -0
  170. package/docs/plugin-template/codex/package.json +17 -0
  171. package/docs/plugin-template/opencode/.github/workflows/build.yml +61 -0
  172. package/docs/plugin-template/opencode/README.md +121 -0
  173. package/docs/plugin-template/opencode/package.json +25 -0
  174. package/package.json +36 -28
  175. package/agents/architect/meta.yml +0 -13
  176. package/agents/designer/meta.yml +0 -13
  177. package/agents/engineer/meta.yml +0 -11
  178. package/agents/postdoc/meta.yml +0 -13
  179. package/agents/researcher/meta.yml +0 -12
  180. package/agents/reviewer/meta.yml +0 -12
  181. package/agents/strategist/meta.yml +0 -13
  182. package/agents/tester/meta.yml +0 -12
  183. package/agents/writer/meta.yml +0 -11
  184. package/conformance/README.md +0 -311
  185. package/conformance/examples/plan.extension.schema.example.json +0 -25
  186. package/conformance/lifecycle/README.md +0 -48
  187. package/conformance/lifecycle/agent-complete.json +0 -44
  188. package/conformance/lifecycle/agent-resume.json +0 -43
  189. package/conformance/lifecycle/agent-spawn.json +0 -36
  190. package/conformance/lifecycle/memory-access-record.json +0 -27
  191. package/conformance/lifecycle/session-end.json +0 -48
  192. package/conformance/scenarios/full-plan-cycle.json +0 -147
  193. package/conformance/scenarios/task-deps-ordering.json +0 -95
  194. package/conformance/schema/fixture.schema.json +0 -354
  195. package/conformance/state-schemas/agent-tracker.schema.json +0 -63
  196. package/conformance/state-schemas/history.schema.json +0 -134
  197. package/conformance/state-schemas/memory-access.schema.json +0 -36
  198. package/conformance/state-schemas/plan.schema.json +0 -77
  199. package/conformance/state-schemas/tasks.schema.json +0 -98
  200. package/conformance/tools/artifact-write.json +0 -97
  201. package/conformance/tools/context.json +0 -172
  202. package/conformance/tools/history-search.json +0 -219
  203. package/conformance/tools/plan-decide.json +0 -139
  204. package/conformance/tools/plan-start.json +0 -81
  205. package/conformance/tools/plan-status.json +0 -127
  206. package/conformance/tools/plan-update.json +0 -341
  207. package/conformance/tools/task-add.json +0 -156
  208. package/conformance/tools/task-close.json +0 -161
  209. package/conformance/tools/task-list.json +0 -177
  210. package/conformance/tools/task-update.json +0 -167
  211. package/docs/behavioral-contracts.md +0 -145
  212. package/docs/consumer-implementation-guide.md +0 -840
  213. package/docs/memory-lifecycle-contract.md +0 -119
  214. package/docs/nexus-layout.md +0 -224
  215. package/docs/nexus-outputs-contract.md +0 -344
  216. package/docs/nexus-state-overview.md +0 -170
  217. package/docs/nexus-tools-contract.md +0 -438
  218. package/manifest.json +0 -448
  219. package/schema/README.md +0 -69
  220. package/schema/agent.schema.json +0 -23
  221. package/schema/common.schema.json +0 -17
  222. package/schema/manifest.schema.json +0 -78
  223. package/schema/memory-policy.schema.json +0 -98
  224. package/schema/skill.schema.json +0 -54
  225. package/schema/task-exceptions.schema.json +0 -40
  226. package/schema/vocabulary.schema.json +0 -167
  227. package/scripts/.gitkeep +0 -0
  228. package/scripts/conformance-coverage.ts +0 -466
  229. package/scripts/import-from-claude-nexus.ts +0 -403
  230. package/scripts/lib/frontmatter.ts +0 -71
  231. package/scripts/lib/lint.ts +0 -348
  232. package/scripts/lib/structure.ts +0 -159
  233. package/scripts/lib/validate.ts +0 -796
  234. package/scripts/validate.ts +0 -90
  235. package/skills/nx-init/meta.yml +0 -8
  236. package/skills/nx-plan/meta.yml +0 -10
  237. package/skills/nx-run/meta.yml +0 -8
  238. package/skills/nx-sync/meta.yml +0 -7
  239. package/vocabulary/capabilities.yml +0 -65
  240. package/vocabulary/categories.yml +0 -11
  241. package/vocabulary/invocations.yml +0 -147
  242. package/vocabulary/memory_policy.yml +0 -88
  243. package/vocabulary/resume-tiers.yml +0 -11
  244. package/vocabulary/tags.yml +0 -60
  245. package/vocabulary/task-exceptions.yml +0 -29
@@ -0,0 +1,74 @@
1
+ /**
2
+ * src/shared/invocations.ts
3
+ *
4
+ * expandInvocation — cross-harness invocation template expansion.
5
+ *
6
+ * Resolves {{template_name key=value ...}} placeholders in agent/skill body.md
7
+ * to harness-native call syntax as defined in assets/tools/tool-name-map.yml
8
+ * invocations section.
9
+ *
10
+ * Supported templates (4):
11
+ * {{subagent_spawn target_role=<role> prompt=<text> [name=<label>]}}
12
+ * {{skill_activation skill=<name> [mode=<mode>]}}
13
+ * {{task_register label=<text> state=<text>}}
14
+ * {{user_question question=<text> options=<json-array>}}
15
+ */
16
+ export type Harness = "claude" | "opencode" | "codex";
17
+ export interface InvocationTemplate {
18
+ args: string[];
19
+ templates: Record<Harness, string>;
20
+ }
21
+ export interface InvocationsMap {
22
+ [name: string]: InvocationTemplate;
23
+ }
24
+ /**
25
+ * Parse a {{...}} invocation call string into its template name and key=value args.
26
+ *
27
+ * Format: {{template_name key1=value1 key2=value2 ...}}
28
+ * Values may be:
29
+ * - double-quoted strings: "..."
30
+ * - bracket-balanced arrays: [...]
31
+ * - brace-balanced objects: {...}
32
+ * - plain non-whitespace tokens
33
+ *
34
+ * Returns null if the format is invalid.
35
+ */
36
+ export declare function parseInvocationCall(call: string): {
37
+ name: string;
38
+ args: Record<string, string>;
39
+ } | null;
40
+ /**
41
+ * Apply a single invocation template for the given harness.
42
+ *
43
+ * Substitutes {placeholder} tokens in the template string with arg values.
44
+ * Optional args (those not in the args map) are omitted along with their
45
+ * surrounding delimiters when absent.
46
+ *
47
+ * Returns the harness-native syntax string, or an error comment if the
48
+ * template name is unknown or a required arg is missing.
49
+ */
50
+ export declare function applyTemplate(templateStr: string, args: Record<string, string>, templateDef: InvocationTemplate): string;
51
+ /**
52
+ * Expand a single {{...}} invocation expression to harness-native syntax.
53
+ *
54
+ * @param expression The content inside {{ }}, e.g. "subagent_spawn target_role=engineer prompt=Fix the bug"
55
+ * @param harness Target harness: "claude" | "opencode" | "codex"
56
+ * @param invocations Parsed invocations map from tool-name-map.yml
57
+ *
58
+ * Returns the expanded string, or a comment if unknown/invalid.
59
+ */
60
+ export declare function expandInvocationExpression(expression: string, harness: Harness, invocations: InvocationsMap): string;
61
+ /**
62
+ * Expand all {{...}} invocation placeholders in a body string.
63
+ *
64
+ * Uses a balance-counter scanner so that nested `{}` inside argument values
65
+ * (e.g. options=[{label: "Set up"}]) are correctly included in the match.
66
+ *
67
+ * @param input Raw body.md content (may contain multiple {{}} blocks)
68
+ * @param harness Target harness
69
+ * @param invocations Parsed invocations map
70
+ *
71
+ * Returns the body with all {{}} templates replaced by harness-native syntax.
72
+ */
73
+ export declare function expandInvocations(input: string, harness: Harness, invocations: InvocationsMap): string;
74
+ //# sourceMappingURL=invocations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocations.d.ts","sourceRoot":"","sources":["../../../src/shared/invocations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,GAAG,IAAI,CAmGP;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,WAAW,EAAE,kBAAkB,GAC9B,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,cAAc,GAC1B,MAAM,CAiBR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,cAAc,GAC1B,MAAM,CAqDR"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * src/shared/invocations.ts
3
+ *
4
+ * expandInvocation — cross-harness invocation template expansion.
5
+ *
6
+ * Resolves {{template_name key=value ...}} placeholders in agent/skill body.md
7
+ * to harness-native call syntax as defined in assets/tools/tool-name-map.yml
8
+ * invocations section.
9
+ *
10
+ * Supported templates (4):
11
+ * {{subagent_spawn target_role=<role> prompt=<text> [name=<label>]}}
12
+ * {{skill_activation skill=<name> [mode=<mode>]}}
13
+ * {{task_register label=<text> state=<text>}}
14
+ * {{user_question question=<text> options=<json-array>}}
15
+ */
16
+ /**
17
+ * Parse a {{...}} invocation call string into its template name and key=value args.
18
+ *
19
+ * Format: {{template_name key1=value1 key2=value2 ...}}
20
+ * Values may be:
21
+ * - double-quoted strings: "..."
22
+ * - bracket-balanced arrays: [...]
23
+ * - brace-balanced objects: {...}
24
+ * - plain non-whitespace tokens
25
+ *
26
+ * Returns null if the format is invalid.
27
+ */
28
+ export function parseInvocationCall(call) {
29
+ // call is the content inside {{ }}
30
+ const trimmed = call.trim();
31
+ if (!trimmed)
32
+ return null;
33
+ // Extract template name (first token)
34
+ const firstSpace = trimmed.indexOf(" ");
35
+ const name = firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
36
+ const rest = firstSpace === -1 ? "" : trimmed.slice(firstSpace + 1).trim();
37
+ const args = {};
38
+ if (rest) {
39
+ // Manual tokenizer: scan for key=<value> pairs where <value> may contain
40
+ // nested brackets/braces or quoted strings.
41
+ let i = 0;
42
+ while (i < rest.length) {
43
+ // Skip whitespace
44
+ while (i < rest.length && /\s/.test(rest[i]))
45
+ i++;
46
+ if (i >= rest.length)
47
+ break;
48
+ // Read key (word chars up to '=')
49
+ const keyStart = i;
50
+ while (i < rest.length && /\w/.test(rest[i]))
51
+ i++;
52
+ if (i >= rest.length || rest[i] !== "=") {
53
+ // Not a valid key=value pair — skip this token
54
+ while (i < rest.length && !/\s/.test(rest[i]))
55
+ i++;
56
+ continue;
57
+ }
58
+ const key = rest.slice(keyStart, i);
59
+ i++; // consume '='
60
+ if (i >= rest.length)
61
+ break;
62
+ // Read value: dispatch on first character
63
+ let value = "";
64
+ const ch = rest[i];
65
+ if (ch === '"') {
66
+ // Quoted string — scan to closing unescaped quote
67
+ i++; // consume opening quote
68
+ const start = i;
69
+ while (i < rest.length) {
70
+ if (rest[i] === "\\" && i + 1 < rest.length) {
71
+ i += 2; // skip escape sequence
72
+ }
73
+ else if (rest[i] === '"') {
74
+ break;
75
+ }
76
+ else {
77
+ i++;
78
+ }
79
+ }
80
+ value = rest.slice(start, i).replace(/\\"/g, '"');
81
+ if (i < rest.length)
82
+ i++; // consume closing quote
83
+ }
84
+ else if (ch === "[" || ch === "{") {
85
+ // Bracket/brace balanced scan
86
+ const open = ch;
87
+ const close = open === "[" ? "]" : "}";
88
+ let depth = 0;
89
+ const start = i;
90
+ while (i < rest.length) {
91
+ const c = rest[i];
92
+ if (c === '"') {
93
+ // Skip quoted section inside array/object
94
+ i++;
95
+ while (i < rest.length) {
96
+ if (rest[i] === "\\" && i + 1 < rest.length) {
97
+ i += 2;
98
+ }
99
+ else if (rest[i] === '"') {
100
+ i++;
101
+ break;
102
+ }
103
+ else {
104
+ i++;
105
+ }
106
+ }
107
+ continue;
108
+ }
109
+ if (c === open)
110
+ depth++;
111
+ else if (c === close) {
112
+ depth--;
113
+ if (depth === 0) {
114
+ i++; // consume closing bracket
115
+ break;
116
+ }
117
+ }
118
+ i++;
119
+ }
120
+ value = rest.slice(start, i);
121
+ }
122
+ else {
123
+ // Plain non-whitespace token
124
+ const start = i;
125
+ while (i < rest.length && !/\s/.test(rest[i]))
126
+ i++;
127
+ value = rest.slice(start, i);
128
+ }
129
+ if (key)
130
+ args[key] = value;
131
+ }
132
+ }
133
+ return { name, args };
134
+ }
135
+ /**
136
+ * Apply a single invocation template for the given harness.
137
+ *
138
+ * Substitutes {placeholder} tokens in the template string with arg values.
139
+ * Optional args (those not in the args map) are omitted along with their
140
+ * surrounding delimiters when absent.
141
+ *
142
+ * Returns the harness-native syntax string, or an error comment if the
143
+ * template name is unknown or a required arg is missing.
144
+ */
145
+ export function applyTemplate(templateStr, args, templateDef) {
146
+ let result = templateStr;
147
+ // Replace all {key} tokens with their values
148
+ for (const [key, value] of Object.entries(args)) {
149
+ result = result.replace(new RegExp(`\\{${key}\\}`, "g"), value);
150
+ }
151
+ // Remove any remaining optional placeholders that have no value
152
+ // Pattern: `, "description": "{name}"` or similar — strip the whole field segment
153
+ // We handle this by removing ", key: "{remaining_token}"" patterns
154
+ result = result.replace(/,?\s*\w+:\s*"\{[^}]+\}"/g, "");
155
+ // Also handle ", description: {name}" without quotes (less common but defensive)
156
+ result = result.replace(/,?\s*\w+:\s*\{[^}]+\}/g, "");
157
+ return result;
158
+ }
159
+ /**
160
+ * Expand a single {{...}} invocation expression to harness-native syntax.
161
+ *
162
+ * @param expression The content inside {{ }}, e.g. "subagent_spawn target_role=engineer prompt=Fix the bug"
163
+ * @param harness Target harness: "claude" | "opencode" | "codex"
164
+ * @param invocations Parsed invocations map from tool-name-map.yml
165
+ *
166
+ * Returns the expanded string, or a comment if unknown/invalid.
167
+ */
168
+ export function expandInvocationExpression(expression, harness, invocations) {
169
+ const parsed = parseInvocationCall(expression);
170
+ if (!parsed) {
171
+ return `/* [nexus] invalid invocation: ${expression.trim()} */`;
172
+ }
173
+ const def = invocations[parsed.name];
174
+ if (!def) {
175
+ return `/* [nexus] unknown invocation: ${parsed.name} */`;
176
+ }
177
+ const templateStr = def.templates[harness];
178
+ if (!templateStr) {
179
+ return `/* [nexus] no template for harness ${harness}: ${parsed.name} */`;
180
+ }
181
+ return applyTemplate(templateStr, parsed.args, def);
182
+ }
183
+ /**
184
+ * Expand all {{...}} invocation placeholders in a body string.
185
+ *
186
+ * Uses a balance-counter scanner so that nested `{}` inside argument values
187
+ * (e.g. options=[{label: "Set up"}]) are correctly included in the match.
188
+ *
189
+ * @param input Raw body.md content (may contain multiple {{}} blocks)
190
+ * @param harness Target harness
191
+ * @param invocations Parsed invocations map
192
+ *
193
+ * Returns the body with all {{}} templates replaced by harness-native syntax.
194
+ */
195
+ export function expandInvocations(input, harness, invocations) {
196
+ let result = "";
197
+ let i = 0;
198
+ while (i < input.length) {
199
+ // Find the next '{{'
200
+ const openIdx = input.indexOf("{{", i);
201
+ if (openIdx === -1) {
202
+ result += input.slice(i);
203
+ break;
204
+ }
205
+ // Append text before the '{{'
206
+ result += input.slice(i, openIdx);
207
+ i = openIdx + 2; // move past '{{'
208
+ // Scan forward with brace depth to find the matching '}}'
209
+ // We start after '{{', depth tracks inner '{' vs '}'
210
+ let depth = 0;
211
+ let innerStart = i;
212
+ let found = false;
213
+ while (i < input.length) {
214
+ if (input[i] === "{") {
215
+ depth++;
216
+ i++;
217
+ }
218
+ else if (input[i] === "}") {
219
+ if (depth === 0 && input[i + 1] === "}") {
220
+ // Found the closing '}}'
221
+ const inner = input.slice(innerStart, i);
222
+ result += expandInvocationExpression(inner, harness, invocations);
223
+ i += 2; // consume '}}'
224
+ found = true;
225
+ break;
226
+ }
227
+ else if (depth > 0) {
228
+ depth--;
229
+ i++;
230
+ }
231
+ else {
232
+ // Single '}' with no matching depth — treat as literal
233
+ i++;
234
+ }
235
+ }
236
+ else {
237
+ i++;
238
+ }
239
+ }
240
+ if (!found) {
241
+ // No closing '}}' — treat the '{{' as literal text
242
+ result += "{{" + input.slice(innerStart, i);
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ //# sourceMappingURL=invocations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocations.js","sourceRoot":"","sources":["../../../src/shared/invocations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Read a JSON file, returning `defaultValue` when the file does not exist or
3
+ * its content cannot be parsed.
4
+ */
5
+ export declare function readJsonFile<T>(filePath: string, defaultValue: T): Promise<T>;
6
+ /**
7
+ * Atomically write a JSON file.
8
+ * Writes to a unique temp file then renames to the target path.
9
+ * Parent directories are created automatically.
10
+ */
11
+ export declare function writeJsonFile<T>(filePath: string, data: T): Promise<void>;
12
+ /**
13
+ * Read-modify-write a JSON file under a two-layer lock.
14
+ *
15
+ * Layer 1 — in-process promise queue: serialises concurrent calls within the
16
+ * same Node/Bun process with zero overhead and no retry cost.
17
+ *
18
+ * Layer 2 — cross-process `.lock` file (O_EXCL): prevents data races between
19
+ * separate processes. Retries every 100 ms for up to 50 attempts (5 s total).
20
+ * Stale locks (mtime > 30 s) are forcibly removed before retry.
21
+ *
22
+ * The lock is always released even when `updater` throws.
23
+ */
24
+ export declare function updateJsonFileLocked<T>(filePath: string, defaultValue: T, updater: (current: T) => T | Promise<T>): Promise<T>;
25
+ /**
26
+ * Append a single JSON record as one line to a `.jsonl` file.
27
+ *
28
+ * Uses the OS `write(2)` syscall via `appendFileSync`, which is atomic for
29
+ * writes up to ~4 KB on most POSIX filesystems. Lines exceeding that threshold
30
+ * trigger a `console.error` warning but are still written (best-effort).
31
+ *
32
+ * No lock is acquired — concurrent appenders are safe at the line level
33
+ * because each call is a single `write(2)` syscall.
34
+ * Parent directories are created automatically.
35
+ */
36
+ export declare function appendJsonLine(filePath: string, record: unknown): void;
37
+ //# sourceMappingURL=json-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-store.d.ts","sourceRoot":"","sources":["../../../src/shared/json-store.ts"],"names":[],"mappings":"AA0FA;;;GAGG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAenF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,CAAC,EACf,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,CAAC,CAYZ;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAYtE"}
@@ -0,0 +1,163 @@
1
+ import fs from "node:fs/promises";
2
+ import { constants as fsConstants, appendFileSync, mkdirSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ // ---------------------------------------------------------------------------
6
+ // In-process mutex (serialises concurrent calls within the same process)
7
+ // ---------------------------------------------------------------------------
8
+ const inProcessQueues = new Map();
9
+ async function runWithInProcessLock(filePath, action) {
10
+ const previous = inProcessQueues.get(filePath) ?? Promise.resolve();
11
+ let release = () => { };
12
+ const gate = new Promise((resolve) => {
13
+ release = resolve;
14
+ });
15
+ // The entry in the map is the combined promise so the next waiter queues behind both
16
+ const entry = previous.then(() => gate);
17
+ inProcessQueues.set(filePath, entry);
18
+ await previous;
19
+ try {
20
+ return await action();
21
+ }
22
+ finally {
23
+ release();
24
+ entry.finally(() => {
25
+ if (inProcessQueues.get(filePath) === entry) {
26
+ inProcessQueues.delete(filePath);
27
+ }
28
+ });
29
+ }
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Cross-process file-system lock (.lock file, O_EXCL)
33
+ // ---------------------------------------------------------------------------
34
+ const LOCK_RETRY_INTERVAL_MS = 100;
35
+ const LOCK_MAX_RETRIES = 50; // 5 seconds total
36
+ const LOCK_STALE_MS = 30_000; // 30 seconds
37
+ function lockPath(filePath) {
38
+ return `${filePath}.lock`;
39
+ }
40
+ async function acquireFsLock(filePath) {
41
+ const lp = lockPath(filePath);
42
+ for (let attempt = 0; attempt <= LOCK_MAX_RETRIES; attempt++) {
43
+ try {
44
+ // O_EXCL ensures atomic creation — only one process wins
45
+ const fd = await fs.open(lp, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL);
46
+ await fd.close();
47
+ return;
48
+ }
49
+ catch (err) {
50
+ const e = err;
51
+ if (e.code !== "EEXIST")
52
+ throw err;
53
+ // Lock file exists — check if it is stale
54
+ try {
55
+ const stat = await fs.stat(lp);
56
+ const ageMs = Date.now() - stat.mtimeMs;
57
+ if (ageMs > LOCK_STALE_MS) {
58
+ // Stale lock — remove and retry immediately
59
+ await fs.unlink(lp).catch(() => undefined);
60
+ continue;
61
+ }
62
+ }
63
+ catch {
64
+ // stat failed (lock vanished between check and here) — retry
65
+ continue;
66
+ }
67
+ if (attempt === LOCK_MAX_RETRIES) {
68
+ throw new Error(`Failed to acquire lock for "${filePath}" after ${LOCK_MAX_RETRIES} retries`);
69
+ }
70
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
71
+ }
72
+ }
73
+ }
74
+ async function releaseFsLock(filePath) {
75
+ await fs.unlink(lockPath(filePath)).catch(() => undefined);
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Public API
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Read a JSON file, returning `defaultValue` when the file does not exist or
82
+ * its content cannot be parsed.
83
+ */
84
+ export async function readJsonFile(filePath, defaultValue) {
85
+ let raw;
86
+ try {
87
+ raw = await fs.readFile(filePath, "utf8");
88
+ }
89
+ catch (err) {
90
+ const e = err;
91
+ if (e.code === "ENOENT")
92
+ return defaultValue;
93
+ throw err;
94
+ }
95
+ try {
96
+ return JSON.parse(raw);
97
+ }
98
+ catch {
99
+ return defaultValue;
100
+ }
101
+ }
102
+ /**
103
+ * Atomically write a JSON file.
104
+ * Writes to a unique temp file then renames to the target path.
105
+ * Parent directories are created automatically.
106
+ */
107
+ export async function writeJsonFile(filePath, data) {
108
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
109
+ const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}.${randomUUID()}`;
110
+ await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
111
+ await fs.rename(tmpPath, filePath);
112
+ }
113
+ /**
114
+ * Read-modify-write a JSON file under a two-layer lock.
115
+ *
116
+ * Layer 1 — in-process promise queue: serialises concurrent calls within the
117
+ * same Node/Bun process with zero overhead and no retry cost.
118
+ *
119
+ * Layer 2 — cross-process `.lock` file (O_EXCL): prevents data races between
120
+ * separate processes. Retries every 100 ms for up to 50 attempts (5 s total).
121
+ * Stale locks (mtime > 30 s) are forcibly removed before retry.
122
+ *
123
+ * The lock is always released even when `updater` throws.
124
+ */
125
+ export async function updateJsonFileLocked(filePath, defaultValue, updater) {
126
+ return runWithInProcessLock(filePath, async () => {
127
+ await acquireFsLock(filePath);
128
+ try {
129
+ const current = await readJsonFile(filePath, defaultValue);
130
+ const next = await updater(current);
131
+ await writeJsonFile(filePath, next);
132
+ return next;
133
+ }
134
+ finally {
135
+ await releaseFsLock(filePath);
136
+ }
137
+ });
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Append-only JSONL helper
141
+ // ---------------------------------------------------------------------------
142
+ const APPEND_SIZE_WARN_THRESHOLD = 4 * 1024; // 4KB — OS write atomicity limit
143
+ /**
144
+ * Append a single JSON record as one line to a `.jsonl` file.
145
+ *
146
+ * Uses the OS `write(2)` syscall via `appendFileSync`, which is atomic for
147
+ * writes up to ~4 KB on most POSIX filesystems. Lines exceeding that threshold
148
+ * trigger a `console.error` warning but are still written (best-effort).
149
+ *
150
+ * No lock is acquired — concurrent appenders are safe at the line level
151
+ * because each call is a single `write(2)` syscall.
152
+ * Parent directories are created automatically.
153
+ */
154
+ export function appendJsonLine(filePath, record) {
155
+ const line = JSON.stringify(record) + "\n";
156
+ if (line.length > APPEND_SIZE_WARN_THRESHOLD) {
157
+ console.error(`[json-store] appendJsonLine line exceeds ${APPEND_SIZE_WARN_THRESHOLD} bytes ` +
158
+ `(${line.length}) — write may not be atomic on some filesystems. path=${filePath}`);
159
+ }
160
+ mkdirSync(path.dirname(filePath), { recursive: true });
161
+ appendFileSync(filePath, line);
162
+ }
163
+ //# sourceMappingURL=json-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-store.js","sourceRoot":"","sources":["../../../src/shared/json-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare function textResult(obj: unknown): CallToolResult;
3
+ //# sourceMappingURL=mcp-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-utils.d.ts","sourceRoot":"","sources":["../../../src/shared/mcp-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAIvD"}
@@ -0,0 +1,6 @@
1
+ export function textResult(obj) {
2
+ return {
3
+ content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
4
+ };
5
+ }
6
+ //# sourceMappingURL=mcp-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-utils.js","sourceRoot":"","sources":["../../../src/shared/mcp-utils.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 프로젝트 루트 해석. 우선순위:
3
+ * 1. NEXUS_PROJECT_ROOT env (테스트·명시 주입용)
4
+ * 2. git rev-parse --show-toplevel (cwd 혹은 인자 기준)
5
+ * 3. cwd 상승하며 .git 수동 탐색
6
+ * 4. fallback: start 자체
7
+ */
8
+ export declare function findProjectRoot(cwd?: string): string;
9
+ /** .nexus/ 루트 경로 getter */
10
+ export declare function getNexusRoot(cwd?: string): string;
11
+ /** .nexus/state/ 경로 getter */
12
+ export declare function getStateRoot(cwd?: string): string;
13
+ /** 현재 git 브랜치명 반환. detached HEAD면 "HEAD" 또는 빈 문자열, git 없으면 빈 문자열 */
14
+ export declare function getCurrentBranch(cwd?: string): string;
15
+ /** 디렉토리 생성 (재귀). 이미 존재하면 idempotent */
16
+ export declare function ensureDir(p: string): void;
17
+ /** NEXUS_SESSION_ID env 우선, 없으면 '<branch>-<pid>' 또는 'unknown-<pid>' */
18
+ export declare function getSessionId(cwd?: string): string;
19
+ /** .nexus/state/<session_id>/ 경로 */
20
+ export declare function getSessionRoot(cwd?: string): string;
21
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/shared/paths.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBpD;AAED,2BAA2B;AAC3B,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED,uCAAuC;AACvC,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAEzC;AAOD,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED,oCAAoC;AACpC,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD"}