syntaur 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dashboard/dist/assets/{_basePickBy-BQIP1Ca7.js → _basePickBy-DTYUlCEg.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-BnBWRwT7.js → _baseUniq-C0Y4HRd5.js} +1 -1
- package/dashboard/dist/assets/{arc-BYWL4eq0.js → arc-BFx2eqN9.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CD_SWPSa.js → architectureDiagram-2XIMDMQ5-Erol1JD6.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BS1ZbFBU.js → blockDiagram-WCTKOSBZ-kSkh6VkS.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-D99yg-l2.js → c4Diagram-IC4MRINW-C04oKzvX.js} +1 -1
- package/dashboard/dist/assets/channel-C82tBKZ7.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BkN9IORC.js → chunk-4BX2VUAB-C3t0tXt-.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BQPHWefV.js → chunk-55IACEB6-2cnyEL0b.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-CNcExMdx.js → chunk-FMBD7UC4-DIY9MTNi.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-LXBmftkC.js → chunk-JSJVCQXG-Cw8fpqpE.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-Tqi7zNqq.js → chunk-KX2RTZJC-BAhd66XV.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DkMbx-rW.js → chunk-NQ4KR5QH-RzXwoxk3.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BlrRCfkJ.js → chunk-QZHKN3VN-Dgri4sGz.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-of3XBzMu.js → chunk-WL4C6EOR-DYLj9JRa.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-STOZ51tg.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-STOZ51tg.js +1 -0
- package/dashboard/dist/assets/clone-TzhWk-Bj.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-BlIiyO76.js → cose-bilkent-S5V4N54A-DfY_Fnfu.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CYQjSI9N.js → dagre-KLK3FWXG-CyTKIVSK.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BZHzTKct.js → diagram-E7M64L7V-Krub7Xxo.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-kMP3WqBV.js → diagram-IFDJBPK2-giUl9uHz.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BWSHyFOv.js → diagram-P4PSJMXO-oAtnO3C9.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-B5HrvsPP.js → erDiagram-INFDFZHY-eYaVjXqo.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Dm4ewP7w.js → flowDiagram-PKNHOUZH-or5S0_Sb.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-DB3k27zu.js → ganttDiagram-A5KZAMGK-C9R1lsme.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-G7y6Ey-m.js → gitGraphDiagram-K3NZZRJ6-BQwsDzvp.js} +1 -1
- package/dashboard/dist/assets/{graph-CaM4i6vq.js → graph-EQOX1wg8.js} +1 -1
- package/dashboard/dist/assets/index-Cy7yjuqO.js +500 -0
- package/dashboard/dist/assets/index-u80fISp0.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-JNTUbTjg.js → infoDiagram-LFFYTUFH-BjLlQWxk.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-BZJt1ht8.js → ishikawaDiagram-PHBUUO56-BNjydh4j.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DPcqvl9A.js → journeyDiagram-4ABVD52K-DNzE7TgQ.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-D1D7AuOV.js → kanban-definition-K7BYSVSG-FbNvGx6i.js} +1 -1
- package/dashboard/dist/assets/{layout-BTOh3EDT.js → layout-B2yZvlWs.js} +1 -1
- package/dashboard/dist/assets/{linear-MbCpC_Cg.js → linear-p68yY_14.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-CYbhqlNy.js → mermaid.core-D558akcW.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CwYCISFH.js → mindmap-definition-YRQLILUH-CZPgesSK.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-5qfZ73SG.js → pieDiagram-SKSYHLDU-CdXMWspp.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-WI8y1sQ_.js → quadrantDiagram-337W2JSQ-D7tq22ZY.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BFlD0ZTS.js → requirementDiagram-Z7DCOOCP-ByZxUSmd.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bdckv1Se.js → sankeyDiagram-WA2Y5GQK-CZon9rRY.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DgzxKAlZ.js → sequenceDiagram-2WXFIKYE-nbELB6rb.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DO4OXahC.js → stateDiagram-RAJIS63D-D_OPKr5B.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-4pLM5B3m.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BBB01JWw.js → timeline-definition-YZTLITO2-Cxuvk1D2.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-Dr0jb8op.js → treemap-KZPCXAKY-C0CRpL92.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-D40KFl2o.js → vennDiagram-LZ73GAT5-DijCj6M3.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DBUmWQfT.js → xychartDiagram-JWTSCODW-CdpE0oRi.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +429 -39
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +658 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/README.md +3 -3
- package/platforms/claude-code/agents/syntaur-expert.md +12 -4
- package/platforms/claude-code/commands/save-session-summary/save-session-summary.md +24 -0
- package/platforms/claude-code/hooks/hooks.json +10 -0
- package/platforms/claude-code/hooks/session-start.sh +26 -1
- package/platforms/claude-code/references/file-ownership.md +2 -1
- package/platforms/claude-code/references/protocol-summary.md +6 -1
- package/platforms/claude-code/skills/track-session/SKILL.md +86 -0
- package/platforms/codex/README.md +2 -2
- package/platforms/codex/agents/syntaur-operator.md +6 -4
- package/platforms/codex/commands/save-session-summary.md +23 -0
- package/platforms/codex/references/file-ownership.md +2 -1
- package/platforms/codex/references/protocol-summary.md +6 -1
- package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +2 -0
- package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +7 -2
- package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +3 -1
- package/vendor/syntaur-skills/skills/save-session-summary/SKILL.md +113 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +23 -4
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +2 -1
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +6 -1
- package/dashboard/dist/assets/channel-Df6VrFK5.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-CyfzumTY.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CyfzumTY.js +0 -1
- package/dashboard/dist/assets/clone-CMs4Aqrx.js +0 -1
- package/dashboard/dist/assets/index-B4QMu-Oq.css +0 -1
- package/dashboard/dist/assets/index-BBWZjPBC.js +0 -495
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-o8bgX-J3.js +0 -1
package/dist/index.js
CHANGED
|
@@ -2017,8 +2017,9 @@ ${todosSection}## Context
|
|
|
2017
2017
|
- [Progress](./progress.md)
|
|
2018
2018
|
- [Comments](./comments.md)
|
|
2019
2019
|
- [Scratchpad](./scratchpad.md)
|
|
2020
|
-
- [Handoff](./handoff.md)
|
|
2020
|
+
- [Handoff](./handoff.md) \u2014 cross-ticket outbound
|
|
2021
2021
|
- [Decision Record](./decision-record.md)
|
|
2022
|
+
- [Sessions](./sessions/) \u2014 per-session continuity summaries (one \`<session-id>/summary.md\` per session)
|
|
2022
2023
|
`;
|
|
2023
2024
|
}
|
|
2024
2025
|
var init_assignment = __esm({
|
|
@@ -2072,6 +2073,13 @@ var init_handoff = __esm({
|
|
|
2072
2073
|
}
|
|
2073
2074
|
});
|
|
2074
2075
|
|
|
2076
|
+
// src/templates/session-summary.ts
|
|
2077
|
+
var init_session_summary = __esm({
|
|
2078
|
+
"src/templates/session-summary.ts"() {
|
|
2079
|
+
"use strict";
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2075
2083
|
// src/templates/progress.ts
|
|
2076
2084
|
function renderProgress(params2) {
|
|
2077
2085
|
return `---
|
|
@@ -2322,8 +2330,11 @@ You are working within the Syntaur protocol for multi-agent project coordination
|
|
|
2322
2330
|
progress.md # Agent-writable, append-only: timestamped progress log
|
|
2323
2331
|
comments.md # CLI-mediated: threaded questions/notes/feedback (via \`syntaur comment\`)
|
|
2324
2332
|
scratchpad.md # Agent-writable: working notes
|
|
2325
|
-
handoff.md # Agent-writable: append-only
|
|
2333
|
+
handoff.md # Agent-writable: append-only cross-ticket outbound at completion
|
|
2326
2334
|
decision-record.md # Agent-writable: append-only decision log
|
|
2335
|
+
sessions/
|
|
2336
|
+
<session-id>/
|
|
2337
|
+
summary.md # Agent-writable: per-session continuity (single doc, overwritten)
|
|
2327
2338
|
resources/
|
|
2328
2339
|
_index.md # Derived (read-only)
|
|
2329
2340
|
<resource-slug>.md # Shared-writable
|
|
@@ -2339,13 +2350,15 @@ You are working within the Syntaur protocol for multi-agent project coordination
|
|
|
2339
2350
|
scratchpad.md
|
|
2340
2351
|
handoff.md
|
|
2341
2352
|
decision-record.md
|
|
2353
|
+
sessions/<session-id>/summary.md # Per-session continuity (same as project-nested)
|
|
2342
2354
|
\`\`\`
|
|
2343
2355
|
|
|
2344
2356
|
## Write Boundary Rules (CRITICAL)
|
|
2345
2357
|
|
|
2346
2358
|
### Files you may WRITE:
|
|
2347
2359
|
1. **Your assignment folder** -- only the assignment you are currently working on:
|
|
2348
|
-
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md
|
|
2360
|
+
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\` (cross-ticket outbound at completion), \`decision-record.md\`
|
|
2361
|
+
- \`sessions/<session-id>/summary.md\` -- per-session continuity (single doc per session id, overwritten on save). Distinct from \`handoff.md\`.
|
|
2349
2362
|
- Path (project-nested): \`~/.syntaur/projects/<project>/assignments/<your-assignment>/\`
|
|
2350
2363
|
- Path (standalone): \`~/.syntaur/assignments/<your-assignment-uuid>/\`
|
|
2351
2364
|
2. **Shared resources and memories** at the project level:
|
|
@@ -2449,7 +2462,8 @@ Before starting work, read these files in order:
|
|
|
2449
2462
|
3. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
2450
2463
|
4. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
2451
2464
|
5. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
2452
|
-
6. \`${params2.assignmentDir}/handoff.md\` --
|
|
2465
|
+
6. \`${params2.assignmentDir}/handoff.md\` -- cross-ticket outbound history (entries from prior agents/humans handing this assignment off)
|
|
2466
|
+
7. The latest \`${params2.assignmentDir}/sessions/<sid>/summary.md\` if present -- previous-session continuity (selected by \`summary.md\` file mtime; read it for "what was done / what's next" before resuming work in flight)
|
|
2453
2467
|
|
|
2454
2468
|
## Your Writable Files
|
|
2455
2469
|
|
|
@@ -2460,6 +2474,7 @@ You may write directly to these files inside your assignment folder:
|
|
|
2460
2474
|
- \`${params2.assignmentDir}/scratchpad.md\`
|
|
2461
2475
|
- \`${params2.assignmentDir}/handoff.md\`
|
|
2462
2476
|
- \`${params2.assignmentDir}/decision-record.md\`
|
|
2477
|
+
- \`${params2.assignmentDir}/sessions/<session-id>/summary.md\` (per-session continuity)
|
|
2463
2478
|
|
|
2464
2479
|
Do NOT edit \`${params2.assignmentDir}/comments.md\` directly \u2014 use \`syntaur comment\`. Do NOT edit other assignments' files \u2014 use \`syntaur request\` for cross-assignment todos.
|
|
2465
2480
|
|
|
@@ -2495,7 +2510,8 @@ If the global Syntaur Codex plugin is installed, prefer these workflows instead
|
|
|
2495
2510
|
- \`create-assignment\` -- create a new assignment (use \`--type <bug|feature|chore|...>\` to classify; use \`--one-off\` to create a standalone assignment at \`~/.syntaur/assignments/<uuid>/\` with no parent project)
|
|
2496
2511
|
- \`grab-assignment\` -- claim work, create \`.syntaur/context.json\`, and register a session
|
|
2497
2512
|
- \`plan-assignment\` -- write a versioned plan file (\`plan.md\`, \`plan-v2.md\`, ...) and link it from the \`## Todos\` section of \`assignment.md\`
|
|
2498
|
-
- \`complete-assignment\` --
|
|
2513
|
+
- \`complete-assignment\` -- write the cross-ticket \`handoff.md\` entry, append a final entry to \`progress.md\`, close the session, and transition state
|
|
2514
|
+
- \`save-session-summary\` -- write per-session continuity at \`<assignmentDir>/sessions/<sessionId>/summary.md\` for resume across sessions of the same agent. Codex has no \`PreCompact\` hook event \u2014 invoke this manually before compaction or session end.
|
|
2499
2515
|
- \`track-session\` -- manage tracked tmux sessions for the dashboard
|
|
2500
2516
|
|
|
2501
2517
|
If the plugin is unavailable, follow the same workflow manually with the \`syntaur\` CLI and keep the protocol files current yourself.
|
|
@@ -2509,7 +2525,8 @@ Before starting work, read these files in order:
|
|
|
2509
2525
|
4. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
2510
2526
|
5. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
2511
2527
|
6. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
2512
|
-
7. \`${params2.assignmentDir}/handoff.md\` --
|
|
2528
|
+
7. \`${params2.assignmentDir}/handoff.md\` -- cross-ticket outbound history (entries from prior agents/humans handing this assignment off)
|
|
2529
|
+
8. The latest \`${params2.assignmentDir}/sessions/<sid>/summary.md\` if present -- previous-session continuity (read it for "what was done / what's next" before resuming work in flight)
|
|
2513
2530
|
|
|
2514
2531
|
## Context File
|
|
2515
2532
|
|
|
@@ -2537,8 +2554,11 @@ Before starting work, read these files in order:
|
|
|
2537
2554
|
progress.md # Agent-writable, append-only: timestamped progress log
|
|
2538
2555
|
comments.md # CLI-mediated: threaded questions/notes/feedback (via \`syntaur comment\`)
|
|
2539
2556
|
scratchpad.md # Agent-writable: working notes
|
|
2540
|
-
handoff.md # Agent-writable: append-only
|
|
2557
|
+
handoff.md # Agent-writable: append-only cross-ticket outbound at completion
|
|
2541
2558
|
decision-record.md # Agent-writable: append-only decision log
|
|
2559
|
+
sessions/
|
|
2560
|
+
<session-id>/
|
|
2561
|
+
summary.md # Agent-writable: per-session continuity (single doc, overwritten)
|
|
2542
2562
|
resources/
|
|
2543
2563
|
_index.md # Derived (read-only)
|
|
2544
2564
|
<resource-slug>.md # Shared-writable
|
|
@@ -2554,13 +2574,15 @@ Before starting work, read these files in order:
|
|
|
2554
2574
|
scratchpad.md
|
|
2555
2575
|
handoff.md
|
|
2556
2576
|
decision-record.md
|
|
2577
|
+
sessions/<session-id>/summary.md # Per-session continuity (same as project-nested)
|
|
2557
2578
|
\`\`\`
|
|
2558
2579
|
|
|
2559
2580
|
## Write Boundary Rules (CRITICAL)
|
|
2560
2581
|
|
|
2561
2582
|
### Files you may WRITE:
|
|
2562
2583
|
1. **Your assignment folder** -- only the assignment you are currently working on:
|
|
2563
|
-
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md
|
|
2584
|
+
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\` (cross-ticket outbound at completion), \`decision-record.md\`
|
|
2585
|
+
- \`sessions/<session-id>/summary.md\` -- per-session continuity (single doc per session id, overwritten on save). Distinct from \`handoff.md\`.
|
|
2564
2586
|
- Path: \`${params2.assignmentDir}/\`
|
|
2565
2587
|
2. **Shared resources and memories** at the project level:
|
|
2566
2588
|
- \`${params2.projectDir}/resources/<slug>.md\`
|
|
@@ -2639,7 +2661,7 @@ Read each linked playbook and follow the rules in its body section. The \`when_t
|
|
|
2639
2661
|
- Slugs are lowercase, hyphen-separated. For standalone assignments, \`slug\` is display-only; the folder is named by the UUID.
|
|
2640
2662
|
- Always read \`project.md\` at the project level (when project-nested) before starting work.
|
|
2641
2663
|
- Keep \`assignment.md\` acceptance criteria and \`## Todos\` updated as work lands; append timestamped entries to \`progress.md\` (never to \`assignment.md\`).
|
|
2642
|
-
- Keep active plan file(s) current after planning changes
|
|
2664
|
+
- Keep active plan file(s) current after planning changes. Write \`handoff.md\` (via \`complete-assignment\`) at the cross-ticket boundary; write \`sessions/<sid>/summary.md\` (via \`/save-session-summary\`) before compaction or before ending a session mid-assignment so a future session can resume cleanly.
|
|
2643
2665
|
- When requirements shift, supersede the prior plan todo (\`- [x] ~~...~~ (superseded by plan-v<N>)\`) and write a new plan file instead of rewriting the old one.
|
|
2644
2666
|
- Record questions, notes, and feedback via \`syntaur comment\`. Never edit \`comments.md\` directly. Resolve questions via the dashboard UI (toggle on the question entry).
|
|
2645
2667
|
- To route work to another assignment, use \`syntaur request\`.
|
|
@@ -2683,6 +2705,7 @@ var init_templates = __esm({
|
|
|
2683
2705
|
init_plan();
|
|
2684
2706
|
init_scratchpad();
|
|
2685
2707
|
init_handoff();
|
|
2708
|
+
init_session_summary();
|
|
2686
2709
|
init_progress();
|
|
2687
2710
|
init_comments();
|
|
2688
2711
|
init_decision_record();
|
|
@@ -6684,7 +6707,7 @@ __export(launch_exports, {
|
|
|
6684
6707
|
shellQuote: () => shellQuote
|
|
6685
6708
|
});
|
|
6686
6709
|
import { spawn as spawn2 } from "child_process";
|
|
6687
|
-
import { mkdir as
|
|
6710
|
+
import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
|
|
6688
6711
|
import { isAbsolute as isAbsolute3, resolve as resolve32 } from "path";
|
|
6689
6712
|
function formatFallbackCwdWarning(opts) {
|
|
6690
6713
|
const missing = [];
|
|
@@ -6743,7 +6766,7 @@ async function launchAgent(options) {
|
|
|
6743
6766
|
if (warning) console.warn(warning);
|
|
6744
6767
|
}
|
|
6745
6768
|
const contextDir = resolve32(workspaceDir, ".syntaur");
|
|
6746
|
-
await
|
|
6769
|
+
await mkdir6(contextDir, { recursive: true });
|
|
6747
6770
|
const context = {
|
|
6748
6771
|
projectSlug,
|
|
6749
6772
|
assignmentSlug,
|
|
@@ -7070,7 +7093,7 @@ init_create_assignment();
|
|
|
7070
7093
|
init_config2();
|
|
7071
7094
|
import { spawn } from "child_process";
|
|
7072
7095
|
import { createServer as createNetServer } from "net";
|
|
7073
|
-
import { resolve as resolve22, dirname as
|
|
7096
|
+
import { resolve as resolve22, dirname as dirname6 } from "path";
|
|
7074
7097
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7075
7098
|
|
|
7076
7099
|
// src/dashboard/server.ts
|
|
@@ -9536,37 +9559,62 @@ init_fs_migration();
|
|
|
9536
9559
|
// src/dashboard/api-todos.ts
|
|
9537
9560
|
init_parser2();
|
|
9538
9561
|
init_fs();
|
|
9562
|
+
init_paths();
|
|
9539
9563
|
import { Router as Router5 } from "express";
|
|
9540
9564
|
import { readdir as readdir9 } from "fs/promises";
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
}
|
|
9546
|
-
return value ?? "";
|
|
9547
|
-
}
|
|
9565
|
+
import { resolve as resolvePath, dirname as dirname4 } from "path";
|
|
9566
|
+
import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
|
|
9567
|
+
|
|
9568
|
+
// src/dashboard/todos-locks.ts
|
|
9548
9569
|
var writeLocks = /* @__PURE__ */ new Map();
|
|
9549
9570
|
function withLock(lockKey, fn) {
|
|
9550
9571
|
const prev = writeLocks.get(lockKey) ?? Promise.resolve();
|
|
9551
9572
|
const next = prev.then(fn);
|
|
9552
|
-
writeLocks.set(
|
|
9553
|
-
|
|
9554
|
-
|
|
9573
|
+
writeLocks.set(
|
|
9574
|
+
lockKey,
|
|
9575
|
+
next.then(
|
|
9576
|
+
() => {
|
|
9577
|
+
},
|
|
9578
|
+
() => {
|
|
9579
|
+
}
|
|
9580
|
+
)
|
|
9581
|
+
);
|
|
9555
9582
|
return next;
|
|
9556
9583
|
}
|
|
9557
9584
|
function wsLock(workspace, fn) {
|
|
9558
9585
|
return withLock(`ws:${workspace}`, fn);
|
|
9559
9586
|
}
|
|
9587
|
+
function projLock(slug, fn) {
|
|
9588
|
+
return withLock(`proj:${slug}`, fn);
|
|
9589
|
+
}
|
|
9590
|
+
function withTwoLocks(keyA, keyB, fn) {
|
|
9591
|
+
if (keyA === keyB) return withLock(keyA, fn);
|
|
9592
|
+
const [first, second] = keyA < keyB ? [keyA, keyB] : [keyB, keyA];
|
|
9593
|
+
return withLock(first, () => withLock(second, fn));
|
|
9594
|
+
}
|
|
9595
|
+
|
|
9596
|
+
// src/dashboard/api-todos.ts
|
|
9597
|
+
init_slug();
|
|
9598
|
+
var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
|
|
9599
|
+
function getWorkspaceParam(value) {
|
|
9600
|
+
if (Array.isArray(value)) {
|
|
9601
|
+
return value[0] ?? "";
|
|
9602
|
+
}
|
|
9603
|
+
return value ?? "";
|
|
9604
|
+
}
|
|
9560
9605
|
function touchItem(item) {
|
|
9561
9606
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9562
9607
|
if (item.createdAt === null) item.createdAt = now;
|
|
9563
9608
|
item.updatedAt = now;
|
|
9564
9609
|
}
|
|
9565
|
-
function createTodosRouter(todosDir2, broadcast) {
|
|
9610
|
+
function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
9566
9611
|
const router = Router5();
|
|
9567
9612
|
function broadcastUpdate() {
|
|
9568
9613
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9569
9614
|
}
|
|
9615
|
+
function broadcastProject(slug) {
|
|
9616
|
+
broadcast({ type: "todos-updated", projectSlug: slug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9617
|
+
}
|
|
9570
9618
|
function validateWorkspace(req, res, next) {
|
|
9571
9619
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
9572
9620
|
if (workspace && !WORKSPACE_REGEX.test(workspace)) {
|
|
@@ -9965,7 +10013,7 @@ workspace: ${workspace}
|
|
|
9965
10013
|
items.push(item);
|
|
9966
10014
|
}
|
|
9967
10015
|
const scopeLabel = workspace === "_global" ? "_global" : `workspace:${workspace}`;
|
|
9968
|
-
const { resolve:
|
|
10016
|
+
const { resolve: resolvePath2 } = await import("path");
|
|
9969
10017
|
const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
9970
10018
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
9971
10019
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
@@ -9995,18 +10043,18 @@ workspace: ${workspace}
|
|
|
9995
10043
|
const parts = tg.split("/");
|
|
9996
10044
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
9997
10045
|
const config = await readConfig3();
|
|
9998
|
-
assignmentDir =
|
|
10046
|
+
assignmentDir = resolvePath2(config.defaultProjectDir, parts[0], "assignments", parts[1]);
|
|
9999
10047
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
10000
10048
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
10001
|
-
assignmentDir =
|
|
10049
|
+
assignmentDir = resolvePath2(assignmentsDirFn(), tg);
|
|
10002
10050
|
assignmentRef = tg;
|
|
10003
10051
|
} else {
|
|
10004
10052
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
10005
10053
|
}
|
|
10006
|
-
const assignmentMdPath2 =
|
|
10054
|
+
const assignmentMdPath2 = resolvePath2(assignmentDir, "assignment.md");
|
|
10007
10055
|
if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
10008
10056
|
}
|
|
10009
|
-
const assignmentMdPath =
|
|
10057
|
+
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
10010
10058
|
let content = await readFile32(assignmentMdPath, "utf-8");
|
|
10011
10059
|
content = appendTodosToAssignmentBody2(
|
|
10012
10060
|
content,
|
|
@@ -10050,6 +10098,115 @@ workspace: ${workspace}
|
|
|
10050
10098
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
|
|
10051
10099
|
}
|
|
10052
10100
|
});
|
|
10101
|
+
router.post("/:workspace/:id/move", async (req, res) => {
|
|
10102
|
+
try {
|
|
10103
|
+
const sourceWs = getWorkspaceParam(req.params.workspace);
|
|
10104
|
+
const id = req.params.id;
|
|
10105
|
+
const to = req.body?.to;
|
|
10106
|
+
if (!to || typeof to !== "object") {
|
|
10107
|
+
res.status(400).json({ error: "body.to is required" });
|
|
10108
|
+
return;
|
|
10109
|
+
}
|
|
10110
|
+
const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
|
|
10111
|
+
if (targetCount !== 1) {
|
|
10112
|
+
res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
|
|
10113
|
+
return;
|
|
10114
|
+
}
|
|
10115
|
+
if (to.project && !isValidSlug(to.project)) {
|
|
10116
|
+
res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
|
|
10117
|
+
return;
|
|
10118
|
+
}
|
|
10119
|
+
if (to.workspace && !WORKSPACE_REGEX.test(to.workspace)) {
|
|
10120
|
+
res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
|
|
10121
|
+
return;
|
|
10122
|
+
}
|
|
10123
|
+
let target;
|
|
10124
|
+
if (to.global) {
|
|
10125
|
+
target = { kind: "workspace", id: "_global", todosPath: todosDir2, lockKey: "ws:_global" };
|
|
10126
|
+
} else if (to.workspace) {
|
|
10127
|
+
target = { kind: "workspace", id: to.workspace, todosPath: todosDir2, lockKey: `ws:${to.workspace}` };
|
|
10128
|
+
} else {
|
|
10129
|
+
if (!projectsDir2) {
|
|
10130
|
+
res.status(500).json({ error: "Server not configured with projectsDir; cannot move to project scope" });
|
|
10131
|
+
return;
|
|
10132
|
+
}
|
|
10133
|
+
const slug = to.project;
|
|
10134
|
+
const projectMd = resolvePath(projectsDir2, slug, "project.md");
|
|
10135
|
+
if (!await fileExists(projectMd)) {
|
|
10136
|
+
res.status(404).json({ error: `Target project "${slug}" not found` });
|
|
10137
|
+
return;
|
|
10138
|
+
}
|
|
10139
|
+
target = {
|
|
10140
|
+
kind: "project",
|
|
10141
|
+
id: slug,
|
|
10142
|
+
todosPath: projectTodosDir(projectsDir2, slug),
|
|
10143
|
+
lockKey: `proj:${slug}`
|
|
10144
|
+
};
|
|
10145
|
+
}
|
|
10146
|
+
const sourceLockKey = `ws:${sourceWs}`;
|
|
10147
|
+
if (sourceLockKey === target.lockKey) {
|
|
10148
|
+
res.status(400).json({ error: "cannot move to the same scope" });
|
|
10149
|
+
return;
|
|
10150
|
+
}
|
|
10151
|
+
const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
|
|
10152
|
+
const sourceChecklist = await readChecklist(todosDir2, sourceWs);
|
|
10153
|
+
const targetChecklist = await readChecklist(target.todosPath, target.id);
|
|
10154
|
+
const idx = sourceChecklist.items.findIndex((i) => i.id === id);
|
|
10155
|
+
if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
|
|
10156
|
+
if (targetChecklist.items.some((i) => i.id === id)) {
|
|
10157
|
+
return { status: 409, error: "id already exists in target" };
|
|
10158
|
+
}
|
|
10159
|
+
const item = sourceChecklist.items[idx];
|
|
10160
|
+
if (item.planDir) {
|
|
10161
|
+
const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
10162
|
+
if (await fileExists(newPlanDir)) {
|
|
10163
|
+
return { status: 409, error: "plan dir already exists in target" };
|
|
10164
|
+
}
|
|
10165
|
+
await mkdir2(dirname4(newPlanDir), { recursive: true });
|
|
10166
|
+
await rename3(item.planDir, newPlanDir);
|
|
10167
|
+
item.planDir = newPlanDir;
|
|
10168
|
+
}
|
|
10169
|
+
sourceChecklist.items.splice(idx, 1);
|
|
10170
|
+
targetChecklist.items.push(item);
|
|
10171
|
+
await writeChecklist(todosDir2, sourceChecklist);
|
|
10172
|
+
await writeChecklist(target.todosPath, targetChecklist);
|
|
10173
|
+
const sourceLabel = sourceWs === "_global" ? "_global" : `workspace:${sourceWs}`;
|
|
10174
|
+
const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
|
|
10175
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
10176
|
+
await appendLogEntry2(todosDir2, sourceWs, {
|
|
10177
|
+
timestamp: ts,
|
|
10178
|
+
itemIds: [id],
|
|
10179
|
+
items: item.description,
|
|
10180
|
+
session: null,
|
|
10181
|
+
branch: item.branch || null,
|
|
10182
|
+
summary: `Moved to ${targetLabel}`,
|
|
10183
|
+
blockers: null,
|
|
10184
|
+
status: null
|
|
10185
|
+
});
|
|
10186
|
+
await appendLogEntry2(target.todosPath, target.id, {
|
|
10187
|
+
timestamp: ts,
|
|
10188
|
+
itemIds: [id],
|
|
10189
|
+
items: item.description,
|
|
10190
|
+
session: null,
|
|
10191
|
+
branch: item.branch || null,
|
|
10192
|
+
summary: `Moved from ${sourceLabel}`,
|
|
10193
|
+
blockers: null,
|
|
10194
|
+
status: null
|
|
10195
|
+
});
|
|
10196
|
+
return { status: 200, item };
|
|
10197
|
+
});
|
|
10198
|
+
if (result.status !== 200) {
|
|
10199
|
+
res.status(result.status).json({ error: result.error });
|
|
10200
|
+
return;
|
|
10201
|
+
}
|
|
10202
|
+
broadcastUpdate();
|
|
10203
|
+
if (target.kind === "project") broadcastProject(target.id);
|
|
10204
|
+
else broadcastUpdate();
|
|
10205
|
+
res.json({ moved: id, to: target });
|
|
10206
|
+
} catch (error) {
|
|
10207
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
|
|
10208
|
+
}
|
|
10209
|
+
});
|
|
10053
10210
|
router.post("/:workspace/:id/unblock", async (req, res) => {
|
|
10054
10211
|
try {
|
|
10055
10212
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
@@ -10082,18 +10239,9 @@ init_fs();
|
|
|
10082
10239
|
init_paths();
|
|
10083
10240
|
init_slug();
|
|
10084
10241
|
import { Router as Router6 } from "express";
|
|
10085
|
-
import { mkdir as
|
|
10086
|
-
import { resolve as resolve19 } from "path";
|
|
10087
|
-
var
|
|
10088
|
-
function projLock(slug, fn) {
|
|
10089
|
-
const key = `proj:${slug}`;
|
|
10090
|
-
const prev = writeLocks2.get(key) ?? Promise.resolve();
|
|
10091
|
-
const next = prev.then(fn);
|
|
10092
|
-
writeLocks2.set(key, next.then(() => {
|
|
10093
|
-
}, () => {
|
|
10094
|
-
}));
|
|
10095
|
-
return next;
|
|
10096
|
-
}
|
|
10242
|
+
import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
|
|
10243
|
+
import { resolve as resolve19, dirname as dirname5 } from "path";
|
|
10244
|
+
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
10097
10245
|
function touchItem2(item) {
|
|
10098
10246
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10099
10247
|
if (item.createdAt === null) item.createdAt = now;
|
|
@@ -10112,7 +10260,7 @@ async function projectExists(projectsDir2, slug) {
|
|
|
10112
10260
|
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
10113
10261
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
10114
10262
|
try {
|
|
10115
|
-
await
|
|
10263
|
+
await mkdir3(todosDir2, { recursive: false });
|
|
10116
10264
|
} catch (err2) {
|
|
10117
10265
|
const code = err2.code;
|
|
10118
10266
|
if (code === "EEXIST") return;
|
|
@@ -10124,7 +10272,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
10124
10272
|
throw err2;
|
|
10125
10273
|
}
|
|
10126
10274
|
try {
|
|
10127
|
-
await
|
|
10275
|
+
await mkdir3(resolve19(todosDir2, "archive"), { recursive: false });
|
|
10128
10276
|
} catch (err2) {
|
|
10129
10277
|
const code = err2.code;
|
|
10130
10278
|
if (code === "EEXIST") return;
|
|
@@ -10139,11 +10287,14 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
10139
10287
|
function notFound(res, slug) {
|
|
10140
10288
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
10141
10289
|
}
|
|
10142
|
-
function createProjectTodosRouter(projectsDir2, broadcast) {
|
|
10290
|
+
function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
10143
10291
|
const router = Router6({ mergeParams: true });
|
|
10144
10292
|
function broadcastUpdate(projectSlug) {
|
|
10145
10293
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10146
10294
|
}
|
|
10295
|
+
function broadcastWorkspace() {
|
|
10296
|
+
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10297
|
+
}
|
|
10147
10298
|
function validateProjectId(req, res, next) {
|
|
10148
10299
|
const slug = getProjectIdParam(params(req).projectId);
|
|
10149
10300
|
if (!slug || !isValidSlug(slug)) {
|
|
@@ -10691,6 +10842,259 @@ workspace: ${slug}
|
|
|
10691
10842
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to unblock todo" });
|
|
10692
10843
|
}
|
|
10693
10844
|
});
|
|
10845
|
+
router.post("/promote", async (req, res) => {
|
|
10846
|
+
try {
|
|
10847
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
10848
|
+
if (!await projectExists(projectsDir2, slug)) {
|
|
10849
|
+
notFound(res, slug);
|
|
10850
|
+
return;
|
|
10851
|
+
}
|
|
10852
|
+
const { todoIds, mode, target, title, type, priority, keepSource } = req.body ?? {};
|
|
10853
|
+
if (!Array.isArray(todoIds) || todoIds.length === 0) {
|
|
10854
|
+
res.status(400).json({ error: "todoIds (non-empty array of strings) is required" });
|
|
10855
|
+
return;
|
|
10856
|
+
}
|
|
10857
|
+
if (mode !== "new-assignment" && mode !== "to-assignment") {
|
|
10858
|
+
res.status(400).json({ error: 'mode must be "new-assignment" or "to-assignment"' });
|
|
10859
|
+
return;
|
|
10860
|
+
}
|
|
10861
|
+
const result = await projLock(slug, async () => {
|
|
10862
|
+
if (!await projectExists(projectsDir2, slug)) return { gone: true };
|
|
10863
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
10864
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
10865
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
10866
|
+
const items = [];
|
|
10867
|
+
for (const id of todoIds) {
|
|
10868
|
+
const item = checklist.items.find((i) => i.id === id);
|
|
10869
|
+
if (!item) return { error: `Todo "${id}" not found` };
|
|
10870
|
+
if (item.status === "completed") return { error: `Todo "${id}" is already completed` };
|
|
10871
|
+
items.push(item);
|
|
10872
|
+
}
|
|
10873
|
+
const scopeLabel = `project:${slug}`;
|
|
10874
|
+
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
10875
|
+
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
10876
|
+
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
10877
|
+
let assignmentRef;
|
|
10878
|
+
let assignmentDir;
|
|
10879
|
+
if (mode === "new-assignment") {
|
|
10880
|
+
const targetProject = target?.project ?? slug;
|
|
10881
|
+
if (!targetProject) return { error: "target.project is required for new-assignment mode" };
|
|
10882
|
+
if (items.length > 1 && !title) return { error: "title is required when promoting multiple todos" };
|
|
10883
|
+
const { createAssignmentCommand: createAssignmentCommand2 } = await Promise.resolve().then(() => (init_create_assignment(), create_assignment_exports));
|
|
10884
|
+
const created = await createAssignmentCommand2(title || items[0].description, {
|
|
10885
|
+
project: targetProject,
|
|
10886
|
+
type,
|
|
10887
|
+
priority,
|
|
10888
|
+
withTodos: true,
|
|
10889
|
+
silent: true
|
|
10890
|
+
});
|
|
10891
|
+
assignmentDir = created.assignmentDir;
|
|
10892
|
+
assignmentRef = `${created.projectSlug}/${created.slug}`;
|
|
10893
|
+
} else {
|
|
10894
|
+
const tg = target?.assignment || "";
|
|
10895
|
+
if (!tg) return { error: "target.assignment is required for to-assignment mode" };
|
|
10896
|
+
if (tg.includes("/")) {
|
|
10897
|
+
const parts = tg.split("/");
|
|
10898
|
+
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
10899
|
+
assignmentDir = resolve19(projectsDir2, parts[0], "assignments", parts[1]);
|
|
10900
|
+
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
10901
|
+
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
10902
|
+
assignmentDir = resolve19(assignmentsDirFn(), tg);
|
|
10903
|
+
assignmentRef = tg;
|
|
10904
|
+
} else {
|
|
10905
|
+
return { error: `Invalid target.assignment "${tg}"` };
|
|
10906
|
+
}
|
|
10907
|
+
const assignmentMdPath2 = resolve19(assignmentDir, "assignment.md");
|
|
10908
|
+
if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
10909
|
+
}
|
|
10910
|
+
const assignmentMdPath = resolve19(assignmentDir, "assignment.md");
|
|
10911
|
+
let content = await readFile14(assignmentMdPath, "utf-8");
|
|
10912
|
+
content = appendTodosToAssignmentBody2(
|
|
10913
|
+
content,
|
|
10914
|
+
items.map((it) => ({
|
|
10915
|
+
description: it.description,
|
|
10916
|
+
trace: `promoted from t:${it.id} in ${scopeLabel}`
|
|
10917
|
+
}))
|
|
10918
|
+
);
|
|
10919
|
+
content = touchAssignmentUpdated2(content, nowTimestamp3());
|
|
10920
|
+
await writeFileForce(assignmentMdPath, content);
|
|
10921
|
+
if (!keepSource) {
|
|
10922
|
+
for (const item of items) {
|
|
10923
|
+
item.status = "completed";
|
|
10924
|
+
item.session = null;
|
|
10925
|
+
touchItem2(item);
|
|
10926
|
+
}
|
|
10927
|
+
checklist.workspace = slug;
|
|
10928
|
+
await writeChecklist(todosDir2, checklist);
|
|
10929
|
+
for (const item of items) {
|
|
10930
|
+
const entry = {
|
|
10931
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10932
|
+
itemIds: [item.id],
|
|
10933
|
+
items: item.description,
|
|
10934
|
+
session: null,
|
|
10935
|
+
branch: item.branch || null,
|
|
10936
|
+
summary: `Promoted to assignment ${assignmentRef}`,
|
|
10937
|
+
blockers: null,
|
|
10938
|
+
status: null
|
|
10939
|
+
};
|
|
10940
|
+
await appendLogEntry2(todosDir2, slug, entry);
|
|
10941
|
+
}
|
|
10942
|
+
}
|
|
10943
|
+
return { assignmentRef, assignmentDir, promoted: items.map((i) => i.id) };
|
|
10944
|
+
});
|
|
10945
|
+
if ("gone" in result) {
|
|
10946
|
+
notFound(res, slug);
|
|
10947
|
+
return;
|
|
10948
|
+
}
|
|
10949
|
+
if ("error" in result) {
|
|
10950
|
+
res.status(400).json({ error: result.error });
|
|
10951
|
+
return;
|
|
10952
|
+
}
|
|
10953
|
+
broadcastUpdate(slug);
|
|
10954
|
+
res.json(result);
|
|
10955
|
+
} catch (error) {
|
|
10956
|
+
if (error.code === "PROJECT_GONE") {
|
|
10957
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
10958
|
+
return;
|
|
10959
|
+
}
|
|
10960
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to promote todos" });
|
|
10961
|
+
}
|
|
10962
|
+
});
|
|
10963
|
+
router.post("/:id/move", async (req, res) => {
|
|
10964
|
+
try {
|
|
10965
|
+
const sourceSlug = getProjectIdParam(params(req).projectId);
|
|
10966
|
+
const id = params(req).id ?? "";
|
|
10967
|
+
if (!await projectExists(projectsDir2, sourceSlug)) {
|
|
10968
|
+
notFound(res, sourceSlug);
|
|
10969
|
+
return;
|
|
10970
|
+
}
|
|
10971
|
+
const to = req.body?.to;
|
|
10972
|
+
if (!to || typeof to !== "object") {
|
|
10973
|
+
res.status(400).json({ error: "body.to is required" });
|
|
10974
|
+
return;
|
|
10975
|
+
}
|
|
10976
|
+
const targetCount = [Boolean(to.workspace), Boolean(to.project), Boolean(to.global)].filter(Boolean).length;
|
|
10977
|
+
if (targetCount !== 1) {
|
|
10978
|
+
res.status(400).json({ error: "body.to must specify exactly one of workspace, project, or global" });
|
|
10979
|
+
return;
|
|
10980
|
+
}
|
|
10981
|
+
if (to.project && !isValidSlug(to.project)) {
|
|
10982
|
+
res.status(400).json({ error: `Invalid target project slug: "${to.project}"` });
|
|
10983
|
+
return;
|
|
10984
|
+
}
|
|
10985
|
+
if (to.workspace && !WORKSPACE_REGEX2.test(to.workspace)) {
|
|
10986
|
+
res.status(400).json({ error: `Invalid target workspace name: "${to.workspace}"` });
|
|
10987
|
+
return;
|
|
10988
|
+
}
|
|
10989
|
+
let target;
|
|
10990
|
+
if (to.global) {
|
|
10991
|
+
if (!workspaceTodosDir) {
|
|
10992
|
+
res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to global scope" });
|
|
10993
|
+
return;
|
|
10994
|
+
}
|
|
10995
|
+
target = { kind: "workspace", id: "_global", todosPath: workspaceTodosDir, lockKey: "ws:_global" };
|
|
10996
|
+
} else if (to.workspace) {
|
|
10997
|
+
if (!workspaceTodosDir) {
|
|
10998
|
+
res.status(500).json({ error: "Server not configured with workspaceTodosDir; cannot move to workspace scope" });
|
|
10999
|
+
return;
|
|
11000
|
+
}
|
|
11001
|
+
target = { kind: "workspace", id: to.workspace, todosPath: workspaceTodosDir, lockKey: `ws:${to.workspace}` };
|
|
11002
|
+
} else {
|
|
11003
|
+
const tslug = to.project;
|
|
11004
|
+
if (!await projectExists(projectsDir2, tslug)) {
|
|
11005
|
+
res.status(404).json({ error: `Target project "${tslug}" not found` });
|
|
11006
|
+
return;
|
|
11007
|
+
}
|
|
11008
|
+
target = {
|
|
11009
|
+
kind: "project",
|
|
11010
|
+
id: tslug,
|
|
11011
|
+
todosPath: projectTodosDir(projectsDir2, tslug),
|
|
11012
|
+
lockKey: `proj:${tslug}`
|
|
11013
|
+
};
|
|
11014
|
+
}
|
|
11015
|
+
const sourceLockKey = `proj:${sourceSlug}`;
|
|
11016
|
+
if (sourceLockKey === target.lockKey) {
|
|
11017
|
+
res.status(400).json({ error: "cannot move to the same scope" });
|
|
11018
|
+
return;
|
|
11019
|
+
}
|
|
11020
|
+
const result = await withTwoLocks(sourceLockKey, target.lockKey, async () => {
|
|
11021
|
+
if (!await projectExists(projectsDir2, sourceSlug)) return { status: "gone" };
|
|
11022
|
+
if (target.kind === "project" && !await projectExists(projectsDir2, target.id)) {
|
|
11023
|
+
return { status: "targetGone" };
|
|
11024
|
+
}
|
|
11025
|
+
await ensureProjectTodosDir(projectsDir2, sourceSlug);
|
|
11026
|
+
const sourceTodosDir = projectTodosDir(projectsDir2, sourceSlug);
|
|
11027
|
+
const sourceChecklist = await readChecklist(sourceTodosDir, sourceSlug);
|
|
11028
|
+
const targetChecklist = await readChecklist(target.todosPath, target.id);
|
|
11029
|
+
const idx = sourceChecklist.items.findIndex((i) => i.id === id);
|
|
11030
|
+
if (idx === -1) return { status: 404, error: `Todo "${id}" not found` };
|
|
11031
|
+
if (targetChecklist.items.some((i) => i.id === id)) {
|
|
11032
|
+
return { status: 409, error: "id already exists in target" };
|
|
11033
|
+
}
|
|
11034
|
+
const item = sourceChecklist.items[idx];
|
|
11035
|
+
if (item.planDir) {
|
|
11036
|
+
const newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
11037
|
+
if (await fileExists(newPlanDir)) {
|
|
11038
|
+
return { status: 409, error: "plan dir already exists in target" };
|
|
11039
|
+
}
|
|
11040
|
+
await mkdir3(dirname5(newPlanDir), { recursive: true });
|
|
11041
|
+
await rename4(item.planDir, newPlanDir);
|
|
11042
|
+
item.planDir = newPlanDir;
|
|
11043
|
+
}
|
|
11044
|
+
sourceChecklist.items.splice(idx, 1);
|
|
11045
|
+
targetChecklist.items.push(item);
|
|
11046
|
+
sourceChecklist.workspace = sourceSlug;
|
|
11047
|
+
await writeChecklist(sourceTodosDir, sourceChecklist);
|
|
11048
|
+
await writeChecklist(target.todosPath, targetChecklist);
|
|
11049
|
+
const sourceLabel = `project:${sourceSlug}`;
|
|
11050
|
+
const targetLabel = target.kind === "project" ? `project:${target.id}` : target.id === "_global" ? "_global" : `workspace:${target.id}`;
|
|
11051
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
11052
|
+
await appendLogEntry2(sourceTodosDir, sourceSlug, {
|
|
11053
|
+
timestamp: ts,
|
|
11054
|
+
itemIds: [id],
|
|
11055
|
+
items: item.description,
|
|
11056
|
+
session: null,
|
|
11057
|
+
branch: item.branch || null,
|
|
11058
|
+
summary: `Moved to ${targetLabel}`,
|
|
11059
|
+
blockers: null,
|
|
11060
|
+
status: null
|
|
11061
|
+
});
|
|
11062
|
+
await appendLogEntry2(target.todosPath, target.id, {
|
|
11063
|
+
timestamp: ts,
|
|
11064
|
+
itemIds: [id],
|
|
11065
|
+
items: item.description,
|
|
11066
|
+
session: null,
|
|
11067
|
+
branch: item.branch || null,
|
|
11068
|
+
summary: `Moved from ${sourceLabel}`,
|
|
11069
|
+
blockers: null,
|
|
11070
|
+
status: null
|
|
11071
|
+
});
|
|
11072
|
+
return { status: 200, item };
|
|
11073
|
+
});
|
|
11074
|
+
if (result.status === "gone") {
|
|
11075
|
+
notFound(res, sourceSlug);
|
|
11076
|
+
return;
|
|
11077
|
+
}
|
|
11078
|
+
if (result.status === "targetGone") {
|
|
11079
|
+
res.status(404).json({ error: `Target project "${target.id}" not found` });
|
|
11080
|
+
return;
|
|
11081
|
+
}
|
|
11082
|
+
if (result.status !== 200) {
|
|
11083
|
+
res.status(result.status).json({ error: result.error });
|
|
11084
|
+
return;
|
|
11085
|
+
}
|
|
11086
|
+
broadcastUpdate(sourceSlug);
|
|
11087
|
+
if (target.kind === "project") broadcastUpdate(target.id);
|
|
11088
|
+
else broadcastWorkspace();
|
|
11089
|
+
res.json({ moved: id, to: target });
|
|
11090
|
+
} catch (error) {
|
|
11091
|
+
if (error.code === "PROJECT_GONE") {
|
|
11092
|
+
notFound(res, getProjectIdParam(params(req).projectId));
|
|
11093
|
+
return;
|
|
11094
|
+
}
|
|
11095
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to move todo" });
|
|
11096
|
+
}
|
|
11097
|
+
});
|
|
10694
11098
|
return router;
|
|
10695
11099
|
}
|
|
10696
11100
|
|
|
@@ -10704,7 +11108,7 @@ init_fs();
|
|
|
10704
11108
|
init_config2();
|
|
10705
11109
|
import { execFile as execFile2 } from "child_process";
|
|
10706
11110
|
import { promisify as promisify2 } from "util";
|
|
10707
|
-
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as
|
|
11111
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
|
|
10708
11112
|
import { resolve as resolve20, join as join2 } from "path";
|
|
10709
11113
|
import { tmpdir } from "os";
|
|
10710
11114
|
var exec2 = promisify2(execFile2);
|
|
@@ -10916,7 +11320,7 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
10916
11320
|
const localExistsBefore = await fileExists(localPath);
|
|
10917
11321
|
if (backupExistsBefore) {
|
|
10918
11322
|
if (!localExistsBefore) {
|
|
10919
|
-
await
|
|
11323
|
+
await rename5(backupPath, localPath);
|
|
10920
11324
|
} else {
|
|
10921
11325
|
throw new Error(
|
|
10922
11326
|
`Cannot restore "${localPath}": a stale crash-recovery backup exists at ${backupPath} while the current path also exists. Inspect both and remove the one you don't need, then retry.`
|
|
@@ -10928,15 +11332,15 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
10928
11332
|
await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
|
|
10929
11333
|
const localExists = await fileExists(localPath);
|
|
10930
11334
|
if (localExists) {
|
|
10931
|
-
await
|
|
11335
|
+
await rename5(localPath, backupPath);
|
|
10932
11336
|
localMovedAside = true;
|
|
10933
11337
|
}
|
|
10934
|
-
await
|
|
11338
|
+
await rename5(stagingPath, localPath);
|
|
10935
11339
|
await rm2(backupPath, { recursive: true, force: true }).catch(() => {
|
|
10936
11340
|
});
|
|
10937
11341
|
} catch (err2) {
|
|
10938
11342
|
if (localMovedAside && await fileExists(backupPath)) {
|
|
10939
|
-
await
|
|
11343
|
+
await rename5(backupPath, localPath).catch(() => {
|
|
10940
11344
|
});
|
|
10941
11345
|
}
|
|
10942
11346
|
await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
|
|
@@ -11675,8 +12079,8 @@ function createDashboardServer(options) {
|
|
|
11675
12079
|
app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
|
|
11676
12080
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
|
|
11677
12081
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
|
|
11678
|
-
app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
|
|
11679
|
-
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast));
|
|
12082
|
+
app.use("/api/todos", createTodosRouter(todosDir2, broadcast, projectsDir2));
|
|
12083
|
+
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast, todosDir2));
|
|
11680
12084
|
app.use("/api/backup", createBackupRouter());
|
|
11681
12085
|
if (serveStaticUi && dashboardDistPath) {
|
|
11682
12086
|
const sendOpts = { dotfiles: "allow" };
|
|
@@ -11822,7 +12226,7 @@ async function dashboardCommand(options) {
|
|
|
11822
12226
|
port = availablePort;
|
|
11823
12227
|
}
|
|
11824
12228
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
11825
|
-
const packageRoot = resolve22(
|
|
12229
|
+
const packageRoot = resolve22(dirname6(thisFile), "..");
|
|
11826
12230
|
const dashboardDist = resolve22(packageRoot, "dashboard", "dist");
|
|
11827
12231
|
const server = createDashboardServer({
|
|
11828
12232
|
port,
|
|
@@ -12051,14 +12455,14 @@ import {
|
|
|
12051
12455
|
} from "fs/promises";
|
|
12052
12456
|
import { existsSync } from "fs";
|
|
12053
12457
|
import { homedir as homedir2 } from "os";
|
|
12054
|
-
import { basename, dirname as
|
|
12458
|
+
import { basename, dirname as dirname8, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve25 } from "path";
|
|
12055
12459
|
|
|
12056
12460
|
// src/utils/package-root.ts
|
|
12057
12461
|
init_fs();
|
|
12058
|
-
import { dirname as
|
|
12462
|
+
import { dirname as dirname7, resolve as resolve24 } from "path";
|
|
12059
12463
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12060
12464
|
async function findPackageRoot(expectedRelativePath) {
|
|
12061
|
-
let currentDir =
|
|
12465
|
+
let currentDir = dirname7(fileURLToPath3(import.meta.url));
|
|
12062
12466
|
while (true) {
|
|
12063
12467
|
const candidate = resolve24(currentDir, expectedRelativePath);
|
|
12064
12468
|
if (await fileExists(candidate)) {
|
|
@@ -12150,7 +12554,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
12150
12554
|
const info = await lstat(targetDir);
|
|
12151
12555
|
if (info.isSymbolicLink()) {
|
|
12152
12556
|
const symlinkTarget = await readlink(targetDir);
|
|
12153
|
-
const resolvedTarget = resolve25(
|
|
12557
|
+
const resolvedTarget = resolve25(dirname8(targetDir), symlinkTarget);
|
|
12154
12558
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
12155
12559
|
return {
|
|
12156
12560
|
exists: true,
|
|
@@ -12187,15 +12591,15 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
12187
12591
|
);
|
|
12188
12592
|
}
|
|
12189
12593
|
async function installCopy(paths, pluginKind) {
|
|
12190
|
-
await ensureDir(
|
|
12594
|
+
await ensureDir(dirname8(paths.targetDir));
|
|
12191
12595
|
await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
|
|
12192
12596
|
const packageManifest = await readPackageManifest(paths.packageRoot);
|
|
12193
12597
|
await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
|
|
12194
12598
|
}
|
|
12195
12599
|
async function installLink(paths) {
|
|
12196
|
-
await ensureDir(
|
|
12600
|
+
await ensureDir(dirname8(paths.targetDir));
|
|
12197
12601
|
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
12198
|
-
await ensureDir(
|
|
12602
|
+
await ensureDir(dirname8(paths.targetDir));
|
|
12199
12603
|
await symlink(resolve25(paths.sourceDir), paths.targetDir, "dir");
|
|
12200
12604
|
}
|
|
12201
12605
|
async function removeInstallMarker(targetDir) {
|
|
@@ -12250,7 +12654,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
12250
12654
|
};
|
|
12251
12655
|
}
|
|
12252
12656
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
12253
|
-
await ensureDir(
|
|
12657
|
+
await ensureDir(dirname8(manifestPath));
|
|
12254
12658
|
await writeFile6(manifestPath, `${JSON.stringify(marketplace, null, 2)}
|
|
12255
12659
|
`, "utf-8");
|
|
12256
12660
|
}
|
|
@@ -12398,7 +12802,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
12398
12802
|
};
|
|
12399
12803
|
existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
12400
12804
|
existing[name].autoUpdate = true;
|
|
12401
|
-
await ensureDir(
|
|
12805
|
+
await ensureDir(dirname8(manifestPath));
|
|
12402
12806
|
await writeFile6(manifestPath, `${JSON.stringify(existing, null, 2)}
|
|
12403
12807
|
`, "utf-8");
|
|
12404
12808
|
}
|
|
@@ -12433,11 +12837,11 @@ async function ensureClaudeUserMarketplace() {
|
|
|
12433
12837
|
}
|
|
12434
12838
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
12435
12839
|
const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
|
|
12436
|
-
const pluginsDir =
|
|
12840
|
+
const pluginsDir = dirname8(normalizedTargetDir);
|
|
12437
12841
|
if (basename(pluginsDir) !== "plugins") {
|
|
12438
12842
|
return null;
|
|
12439
12843
|
}
|
|
12440
|
-
const rootDir =
|
|
12844
|
+
const rootDir = dirname8(pluginsDir);
|
|
12441
12845
|
const manifestPath = resolve25(rootDir, ".claude-plugin", "marketplace.json");
|
|
12442
12846
|
if (!await fileExists(manifestPath)) {
|
|
12443
12847
|
return null;
|
|
@@ -12603,7 +13007,7 @@ async function installManagedPlugin(options) {
|
|
|
12603
13007
|
};
|
|
12604
13008
|
}
|
|
12605
13009
|
function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
|
|
12606
|
-
const relPath = relative2(
|
|
13010
|
+
const relPath = relative2(dirname8(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
|
|
12607
13011
|
if (relPath === "") {
|
|
12608
13012
|
return ".";
|
|
12609
13013
|
}
|
|
@@ -12640,7 +13044,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
12640
13044
|
};
|
|
12641
13045
|
}
|
|
12642
13046
|
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
12643
|
-
await ensureDir(
|
|
13047
|
+
await ensureDir(dirname8(marketplacePath));
|
|
12644
13048
|
await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
12645
13049
|
`, "utf-8");
|
|
12646
13050
|
}
|
|
@@ -12867,8 +13271,8 @@ async function textPrompt(question, defaultValue) {
|
|
|
12867
13271
|
|
|
12868
13272
|
// src/utils/install-skills.ts
|
|
12869
13273
|
init_fs();
|
|
12870
|
-
import { readFile as readFile17, readdir as readdir11, mkdir as
|
|
12871
|
-
import { dirname as
|
|
13274
|
+
import { readFile as readFile17, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4 } from "fs/promises";
|
|
13275
|
+
import { dirname as dirname9, resolve as resolve26, relative as relative3, join as join3 } from "path";
|
|
12872
13276
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12873
13277
|
import { homedir as homedir3 } from "os";
|
|
12874
13278
|
var REQUIRED_SKILLS = [
|
|
@@ -12877,12 +13281,18 @@ var REQUIRED_SKILLS = [
|
|
|
12877
13281
|
"plan-assignment",
|
|
12878
13282
|
"complete-assignment",
|
|
12879
13283
|
"create-assignment",
|
|
12880
|
-
"create-project"
|
|
13284
|
+
"create-project",
|
|
13285
|
+
"save-session-summary"
|
|
12881
13286
|
];
|
|
12882
13287
|
function getVendoredSkillsDir() {
|
|
12883
|
-
const here =
|
|
13288
|
+
const here = dirname9(fileURLToPath4(import.meta.url));
|
|
12884
13289
|
return resolve26(here, "..", "vendor", "syntaur-skills", "skills");
|
|
12885
13290
|
}
|
|
13291
|
+
function getPlatformSkillsDir(target) {
|
|
13292
|
+
const here = dirname9(fileURLToPath4(import.meta.url));
|
|
13293
|
+
const kind = target === "claude" ? "claude-code" : "codex";
|
|
13294
|
+
return resolve26(here, "..", "platforms", kind, "skills");
|
|
13295
|
+
}
|
|
12886
13296
|
function defaultSkillTargetDir(target) {
|
|
12887
13297
|
if (target === "claude") return resolve26(homedir3(), ".claude", "skills");
|
|
12888
13298
|
return resolve26(homedir3(), ".codex", "skills");
|
|
@@ -12913,7 +13323,7 @@ async function filesEqual(a, b) {
|
|
|
12913
13323
|
}
|
|
12914
13324
|
}
|
|
12915
13325
|
async function copyDir(srcDir, destDir) {
|
|
12916
|
-
await
|
|
13326
|
+
await mkdir4(destDir, { recursive: true });
|
|
12917
13327
|
const entries = await readdir11(srcDir, { withFileTypes: true });
|
|
12918
13328
|
for (const entry of entries) {
|
|
12919
13329
|
const src = join3(srcDir, entry.name);
|
|
@@ -12937,8 +13347,24 @@ async function skillMatches(srcDir, destDir) {
|
|
|
12937
13347
|
if (destFiles.length !== srcFiles.length) return false;
|
|
12938
13348
|
return true;
|
|
12939
13349
|
}
|
|
13350
|
+
async function installSkillDir(srcDir, destDir, skillName, source, force) {
|
|
13351
|
+
if (!await fileExists(destDir)) {
|
|
13352
|
+
await copyDir(srcDir, destDir);
|
|
13353
|
+
return { skill: skillName, status: "installed", targetPath: destDir, source };
|
|
13354
|
+
}
|
|
13355
|
+
if (await skillMatches(srcDir, destDir)) {
|
|
13356
|
+
return { skill: skillName, status: "already-current", targetPath: destDir, source };
|
|
13357
|
+
}
|
|
13358
|
+
if (force) {
|
|
13359
|
+
await rm4(destDir, { recursive: true, force: true });
|
|
13360
|
+
await copyDir(srcDir, destDir);
|
|
13361
|
+
return { skill: skillName, status: "overwritten", targetPath: destDir, source };
|
|
13362
|
+
}
|
|
13363
|
+
return { skill: skillName, status: "differs-preserved", targetPath: destDir, source };
|
|
13364
|
+
}
|
|
12940
13365
|
async function installSkills(options) {
|
|
12941
13366
|
const source = options.sourceDir ?? getVendoredSkillsDir();
|
|
13367
|
+
const platformSource = options.platformSkillsDir ?? getPlatformSkillsDir(options.target);
|
|
12942
13368
|
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
12943
13369
|
const force = options.force ?? false;
|
|
12944
13370
|
if (!await fileExists(source)) {
|
|
@@ -12947,42 +13373,22 @@ async function installSkills(options) {
|
|
|
12947
13373
|
);
|
|
12948
13374
|
}
|
|
12949
13375
|
const results = [];
|
|
12950
|
-
await
|
|
13376
|
+
await mkdir4(targetRoot, { recursive: true });
|
|
12951
13377
|
for (const skill of REQUIRED_SKILLS) {
|
|
12952
13378
|
const srcDir = join3(source, skill);
|
|
12953
|
-
const destDir = join3(targetRoot, skill);
|
|
12954
13379
|
if (!await fileExists(srcDir)) continue;
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
status: "already-current",
|
|
12968
|
-
targetPath: destDir
|
|
12969
|
-
});
|
|
12970
|
-
continue;
|
|
12971
|
-
}
|
|
12972
|
-
if (force) {
|
|
12973
|
-
await rm4(destDir, { recursive: true, force: true });
|
|
12974
|
-
await copyDir(srcDir, destDir);
|
|
12975
|
-
results.push({
|
|
12976
|
-
skill,
|
|
12977
|
-
status: "overwritten",
|
|
12978
|
-
targetPath: destDir
|
|
12979
|
-
});
|
|
12980
|
-
} else {
|
|
12981
|
-
results.push({
|
|
12982
|
-
skill,
|
|
12983
|
-
status: "differs-preserved",
|
|
12984
|
-
targetPath: destDir
|
|
12985
|
-
});
|
|
13380
|
+
const destDir = join3(targetRoot, skill);
|
|
13381
|
+
results.push(await installSkillDir(srcDir, destDir, skill, "shared", force));
|
|
13382
|
+
}
|
|
13383
|
+
if (options.target === "claude" && await fileExists(platformSource)) {
|
|
13384
|
+
const entries = await readdir11(platformSource, { withFileTypes: true });
|
|
13385
|
+
for (const entry of entries) {
|
|
13386
|
+
if (!entry.isDirectory()) continue;
|
|
13387
|
+
const skill = entry.name;
|
|
13388
|
+
if (REQUIRED_SKILLS.includes(skill)) continue;
|
|
13389
|
+
const srcDir = join3(platformSource, skill);
|
|
13390
|
+
const destDir = join3(targetRoot, skill);
|
|
13391
|
+
results.push(await installSkillDir(srcDir, destDir, skill, "platform", force));
|
|
12986
13392
|
}
|
|
12987
13393
|
}
|
|
12988
13394
|
return results;
|
|
@@ -12990,8 +13396,16 @@ async function installSkills(options) {
|
|
|
12990
13396
|
async function uninstallSkills(options) {
|
|
12991
13397
|
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
12992
13398
|
if (!await fileExists(targetRoot)) return [];
|
|
13399
|
+
const known = new Set(REQUIRED_SKILLS);
|
|
13400
|
+
const platformSource = options.platformSkillsDir ?? getPlatformSkillsDir(options.target);
|
|
13401
|
+
if (options.target === "claude" && await fileExists(platformSource)) {
|
|
13402
|
+
const entries = await readdir11(platformSource, { withFileTypes: true });
|
|
13403
|
+
for (const entry of entries) {
|
|
13404
|
+
if (entry.isDirectory()) known.add(entry.name);
|
|
13405
|
+
}
|
|
13406
|
+
}
|
|
12993
13407
|
const removed = [];
|
|
12994
|
-
for (const skill of
|
|
13408
|
+
for (const skill of known) {
|
|
12995
13409
|
const destDir = join3(targetRoot, skill);
|
|
12996
13410
|
if (!await fileExists(destDir)) continue;
|
|
12997
13411
|
const skillMd = join3(destDir, "SKILL.md");
|
|
@@ -13125,16 +13539,17 @@ async function installPluginCommand(options) {
|
|
|
13125
13539
|
}
|
|
13126
13540
|
}
|
|
13127
13541
|
console.log("\nThe plugin is now available in Claude Code.");
|
|
13128
|
-
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project");
|
|
13542
|
+
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project, /track-session, /save-session-summary");
|
|
13129
13543
|
console.log(" Background: syntaur-protocol skill (auto-invoked)");
|
|
13130
|
-
console.log("
|
|
13544
|
+
console.log(" Claude-specific skill: track-session (agent session registration)");
|
|
13545
|
+
console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End + PreCompact (prompts /save-session-summary)");
|
|
13131
13546
|
}
|
|
13132
13547
|
|
|
13133
13548
|
// src/commands/install-statusline.ts
|
|
13134
13549
|
init_paths();
|
|
13135
13550
|
init_fs();
|
|
13136
13551
|
import { readFile as readFile19, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
|
|
13137
|
-
import { resolve as resolve28, dirname as
|
|
13552
|
+
import { resolve as resolve28, dirname as dirname11 } from "path";
|
|
13138
13553
|
import { homedir as homedir4 } from "os";
|
|
13139
13554
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13140
13555
|
|
|
@@ -13142,7 +13557,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
13142
13557
|
init_paths();
|
|
13143
13558
|
init_fs();
|
|
13144
13559
|
import { readFile as readFile18, writeFile as writeFile7 } from "fs/promises";
|
|
13145
|
-
import { resolve as resolve27, dirname as
|
|
13560
|
+
import { resolve as resolve27, dirname as dirname10 } from "path";
|
|
13146
13561
|
import { spawnSync } from "child_process";
|
|
13147
13562
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
13148
13563
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -13263,7 +13678,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
13263
13678
|
env: {
|
|
13264
13679
|
...process.env,
|
|
13265
13680
|
// Force the child to pick up the freshly-written config from install root.
|
|
13266
|
-
HOME:
|
|
13681
|
+
HOME: dirname10(dirname10(statuslineScript))
|
|
13267
13682
|
}
|
|
13268
13683
|
});
|
|
13269
13684
|
if (res.status !== 0) return null;
|
|
@@ -13314,7 +13729,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
13314
13729
|
console.log("(preview mode \u2014 config NOT written)");
|
|
13315
13730
|
return;
|
|
13316
13731
|
}
|
|
13317
|
-
await ensureDir(
|
|
13732
|
+
await ensureDir(dirname10(configPath));
|
|
13318
13733
|
await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13319
13734
|
console.log("Wrote statusline config:");
|
|
13320
13735
|
console.log(` path: ${configPath}`);
|
|
@@ -13341,7 +13756,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
13341
13756
|
async function writeDefaultConfigIfMissing(installRoot) {
|
|
13342
13757
|
const path = getConfigPath(installRoot);
|
|
13343
13758
|
if (await fileExists(path)) return;
|
|
13344
|
-
await ensureDir(
|
|
13759
|
+
await ensureDir(dirname10(path));
|
|
13345
13760
|
const defaultConfig = {
|
|
13346
13761
|
segments: ["git", "assignment", "session"],
|
|
13347
13762
|
separator: " \xB7 "
|
|
@@ -13351,7 +13766,7 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
13351
13766
|
|
|
13352
13767
|
// src/commands/install-statusline.ts
|
|
13353
13768
|
function getPackageStatuslineSource() {
|
|
13354
|
-
const here =
|
|
13769
|
+
const here = dirname11(fileURLToPath5(import.meta.url));
|
|
13355
13770
|
return resolve28(here, "..", "statusline", "statusline.sh");
|
|
13356
13771
|
}
|
|
13357
13772
|
async function readSettingsJson(settingsPath) {
|
|
@@ -13368,7 +13783,7 @@ async function readSettingsJson(settingsPath) {
|
|
|
13368
13783
|
}
|
|
13369
13784
|
}
|
|
13370
13785
|
async function writeSettingsJson(settingsPath, data) {
|
|
13371
|
-
await ensureDir(
|
|
13786
|
+
await ensureDir(dirname11(settingsPath));
|
|
13372
13787
|
await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
13373
13788
|
}
|
|
13374
13789
|
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
@@ -13403,7 +13818,7 @@ function extractExistingCommand(settings) {
|
|
|
13403
13818
|
};
|
|
13404
13819
|
}
|
|
13405
13820
|
async function backupSettings(settingsSnapshot, backupPath) {
|
|
13406
|
-
await ensureDir(
|
|
13821
|
+
await ensureDir(dirname11(backupPath));
|
|
13407
13822
|
await writeFile8(
|
|
13408
13823
|
backupPath,
|
|
13409
13824
|
JSON.stringify(
|
|
@@ -13420,7 +13835,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
|
|
|
13420
13835
|
);
|
|
13421
13836
|
}
|
|
13422
13837
|
async function installScript(sourceScript, destScript, link) {
|
|
13423
|
-
await ensureDir(
|
|
13838
|
+
await ensureDir(dirname11(destScript));
|
|
13424
13839
|
try {
|
|
13425
13840
|
const s = await lstat2(destScript);
|
|
13426
13841
|
if (s.isSymbolicLink() || s.isFile()) {
|
|
@@ -13488,7 +13903,7 @@ exec ${existingCommand}
|
|
|
13488
13903
|
wrapTarget = wrapperPath;
|
|
13489
13904
|
}
|
|
13490
13905
|
}
|
|
13491
|
-
await ensureDir(
|
|
13906
|
+
await ensureDir(dirname11(confPath));
|
|
13492
13907
|
await writeFile8(
|
|
13493
13908
|
confPath,
|
|
13494
13909
|
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
@@ -13677,9 +14092,10 @@ async function installCodexPluginCommand(options) {
|
|
|
13677
14092
|
}
|
|
13678
14093
|
console.log("\nThe plugin is now available to Codex.");
|
|
13679
14094
|
console.log(
|
|
13680
|
-
" Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
|
|
14095
|
+
" Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment, save-session-summary"
|
|
13681
14096
|
);
|
|
13682
14097
|
console.log(" Codex-specific: track-session skill (rollout path aware)");
|
|
14098
|
+
console.log(" Slash commands: /track-session, /save-session-summary (no PreCompact hook on Codex \u2014 invoke manually before compaction)");
|
|
13683
14099
|
console.log(" Hooks: write boundary enforcement, session cleanup");
|
|
13684
14100
|
}
|
|
13685
14101
|
|
|
@@ -14408,6 +14824,19 @@ async function disablePlaybookCommand(slug) {
|
|
|
14408
14824
|
}
|
|
14409
14825
|
}
|
|
14410
14826
|
|
|
14827
|
+
// src/commands/regen-playbook-manifest.ts
|
|
14828
|
+
init_paths();
|
|
14829
|
+
init_playbooks();
|
|
14830
|
+
init_fs();
|
|
14831
|
+
async function regenPlaybookManifestCommand() {
|
|
14832
|
+
const dir = playbooksDir();
|
|
14833
|
+
if (!await fileExists(dir)) {
|
|
14834
|
+
throw new Error(`Playbooks directory not found at ${dir}. Run "syntaur init" first.`);
|
|
14835
|
+
}
|
|
14836
|
+
await rebuildPlaybookManifest(dir);
|
|
14837
|
+
console.log(`Rebuilt playbook manifest at ${dir}/manifest.md`);
|
|
14838
|
+
}
|
|
14839
|
+
|
|
14411
14840
|
// src/commands/todo.ts
|
|
14412
14841
|
init_paths();
|
|
14413
14842
|
init_parser2();
|
|
@@ -14417,7 +14846,7 @@ init_slug();
|
|
|
14417
14846
|
import { Command } from "commander";
|
|
14418
14847
|
import { readFile as readFile23 } from "fs/promises";
|
|
14419
14848
|
import { resolve as resolve36 } from "path";
|
|
14420
|
-
var
|
|
14849
|
+
var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
14421
14850
|
async function resolveScope(options) {
|
|
14422
14851
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
14423
14852
|
if (flagCount > 1) {
|
|
@@ -14435,7 +14864,7 @@ async function resolveScope(options) {
|
|
|
14435
14864
|
return { kind: "project", id: options.project, todosPath: projectTodosDir(config.defaultProjectDir, options.project) };
|
|
14436
14865
|
}
|
|
14437
14866
|
if (options.workspace) {
|
|
14438
|
-
if (!
|
|
14867
|
+
if (!WORKSPACE_REGEX3.test(options.workspace)) {
|
|
14439
14868
|
throw new Error(`Invalid workspace name: "${options.workspace}". Use lowercase letters, numbers, hyphens, and underscores.`);
|
|
14440
14869
|
}
|
|
14441
14870
|
return { kind: "workspace", id: options.workspace, todosPath: todosDir() };
|
|
@@ -14886,7 +15315,7 @@ async function promoteTodos(ids, options) {
|
|
|
14886
15315
|
return;
|
|
14887
15316
|
}
|
|
14888
15317
|
const target = options.toAssignment;
|
|
14889
|
-
const { resolve:
|
|
15318
|
+
const { resolve: resolvePath2 } = await import("path");
|
|
14890
15319
|
const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
14891
15320
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
14892
15321
|
const config = await readConfig3();
|
|
@@ -14897,16 +15326,16 @@ async function promoteTodos(ids, options) {
|
|
|
14897
15326
|
if (parts.length !== 2 || !isValidSlug(parts[0]) || !isValidSlug(parts[1])) {
|
|
14898
15327
|
throw new Error(`Invalid --to-assignment target "${target}". Use <project>/<slug> or a bare UUID.`);
|
|
14899
15328
|
}
|
|
14900
|
-
assignmentDir =
|
|
15329
|
+
assignmentDir = resolvePath2(config.defaultProjectDir, parts[0], "assignments", parts[1]);
|
|
14901
15330
|
displayRef = `${parts[0]}/${parts[1]}`;
|
|
14902
15331
|
} else if (UUID_REGEX.test(target)) {
|
|
14903
|
-
assignmentDir =
|
|
15332
|
+
assignmentDir = resolvePath2(assignmentsDirFn(), target);
|
|
14904
15333
|
displayRef = target;
|
|
14905
15334
|
} else {
|
|
14906
15335
|
throw new Error(`Invalid --to-assignment target "${target}". Use <project>/<slug> or a bare UUID.`);
|
|
14907
15336
|
}
|
|
14908
15337
|
const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
14909
|
-
const assignmentMdPath =
|
|
15338
|
+
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
14910
15339
|
if (!await fileExists2(assignmentMdPath)) {
|
|
14911
15340
|
throw new Error(`Target assignment not found: ${assignmentMdPath}`);
|
|
14912
15341
|
}
|
|
@@ -14922,12 +15351,12 @@ function describeScope(scope) {
|
|
|
14922
15351
|
return `workspace:${scope.id}`;
|
|
14923
15352
|
}
|
|
14924
15353
|
async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
|
|
14925
|
-
const { resolve:
|
|
15354
|
+
const { resolve: resolvePath2 } = await import("path");
|
|
14926
15355
|
const { readFile: readFile32 } = await import("fs/promises");
|
|
14927
15356
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
14928
15357
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
14929
15358
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
14930
|
-
const assignmentMdPath =
|
|
15359
|
+
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
14931
15360
|
let content = await readFile32(assignmentMdPath, "utf-8");
|
|
14932
15361
|
content = appendTodosToAssignmentBody2(
|
|
14933
15362
|
content,
|
|
@@ -15014,6 +15443,90 @@ ${item.description}
|
|
|
15014
15443
|
process.exit(1);
|
|
15015
15444
|
}
|
|
15016
15445
|
});
|
|
15446
|
+
todoCommand.command("move").description("Move a todo between scopes (workspace \u2194 project \u2194 global) without converting it").argument("<id>", "Todo short ID").option("--to-workspace <slug>", "Target workspace slug").option("--to-project <slug>", "Target project slug").option("--to-global", "Move to global todos").option("--workspace <slug>", "Source workspace slug").option("--project <slug>", "Source project slug (mutually exclusive with --workspace/--global)").option("--global", "Source: global todos").action(async (id, options) => {
|
|
15447
|
+
try {
|
|
15448
|
+
await moveTodo(id, options);
|
|
15449
|
+
} catch (error) {
|
|
15450
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
15451
|
+
process.exit(1);
|
|
15452
|
+
}
|
|
15453
|
+
});
|
|
15454
|
+
async function moveTodo(id, options) {
|
|
15455
|
+
const targetCount = [
|
|
15456
|
+
Boolean(options.toWorkspace),
|
|
15457
|
+
Boolean(options.toProject),
|
|
15458
|
+
Boolean(options.toGlobal)
|
|
15459
|
+
].filter(Boolean).length;
|
|
15460
|
+
if (targetCount !== 1) {
|
|
15461
|
+
throw new Error("Specify exactly one of --to-workspace <slug>, --to-project <slug>, --to-global.");
|
|
15462
|
+
}
|
|
15463
|
+
const sourceScope = await resolveScope({
|
|
15464
|
+
project: options.project,
|
|
15465
|
+
workspace: options.workspace,
|
|
15466
|
+
global: options.global
|
|
15467
|
+
});
|
|
15468
|
+
const targetScope = await resolveScope({
|
|
15469
|
+
project: options.toProject,
|
|
15470
|
+
workspace: options.toWorkspace,
|
|
15471
|
+
global: options.toGlobal
|
|
15472
|
+
});
|
|
15473
|
+
if (sourceScope.kind === targetScope.kind && sourceScope.id === targetScope.id) {
|
|
15474
|
+
throw new Error("Source and target scopes are the same; nothing to move.");
|
|
15475
|
+
}
|
|
15476
|
+
const sourceChecklist = await readChecklist(sourceScope.todosPath, sourceScope.id);
|
|
15477
|
+
const targetChecklist = sourceScope.todosPath === targetScope.todosPath && sourceScope.id === targetScope.id ? sourceChecklist : await readChecklist(targetScope.todosPath, targetScope.id);
|
|
15478
|
+
const idx = sourceChecklist.items.findIndex((i) => i.id === id);
|
|
15479
|
+
if (idx === -1) {
|
|
15480
|
+
throw new Error(`Todo [t:${id}] not found in scope ${describeScope(sourceScope)}.`);
|
|
15481
|
+
}
|
|
15482
|
+
const item = sourceChecklist.items[idx];
|
|
15483
|
+
if (targetChecklist.items.some((i) => i.id === id)) {
|
|
15484
|
+
throw new Error(`Todo id [t:${id}] already exists in target scope ${describeScope(targetScope)}; refusing to move (collision).`);
|
|
15485
|
+
}
|
|
15486
|
+
if (item.planDir) {
|
|
15487
|
+
const newPlanDir = todoPlanDir(targetScope.todosPath, targetScope.id, id);
|
|
15488
|
+
if (await fileExists(newPlanDir)) {
|
|
15489
|
+
throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
|
|
15490
|
+
}
|
|
15491
|
+
const { rename: rename6, mkdir: mkdir7 } = await import("fs/promises");
|
|
15492
|
+
const { dirname: dirname16 } = await import("path");
|
|
15493
|
+
await mkdir7(dirname16(newPlanDir), { recursive: true });
|
|
15494
|
+
await rename6(item.planDir, newPlanDir);
|
|
15495
|
+
item.planDir = newPlanDir;
|
|
15496
|
+
}
|
|
15497
|
+
sourceChecklist.items.splice(idx, 1);
|
|
15498
|
+
targetChecklist.items.push(item);
|
|
15499
|
+
await writeChecklist(sourceScope.todosPath, sourceChecklist);
|
|
15500
|
+
if (targetChecklist !== sourceChecklist) {
|
|
15501
|
+
await writeChecklist(targetScope.todosPath, targetChecklist);
|
|
15502
|
+
}
|
|
15503
|
+
const sourceLabel = describeScope(sourceScope);
|
|
15504
|
+
const targetLabel = describeScope(targetScope);
|
|
15505
|
+
const ts = nowISO();
|
|
15506
|
+
const sourceEntry = {
|
|
15507
|
+
timestamp: ts,
|
|
15508
|
+
itemIds: [id],
|
|
15509
|
+
items: item.description,
|
|
15510
|
+
session: null,
|
|
15511
|
+
branch: item.branch || null,
|
|
15512
|
+
summary: `Moved to ${targetLabel}`,
|
|
15513
|
+
blockers: null,
|
|
15514
|
+
status: null
|
|
15515
|
+
};
|
|
15516
|
+
const targetEntry = {
|
|
15517
|
+
timestamp: ts,
|
|
15518
|
+
itemIds: [id],
|
|
15519
|
+
items: item.description,
|
|
15520
|
+
session: null,
|
|
15521
|
+
branch: item.branch || null,
|
|
15522
|
+
summary: `Moved from ${sourceLabel}`,
|
|
15523
|
+
blockers: null,
|
|
15524
|
+
status: null
|
|
15525
|
+
};
|
|
15526
|
+
await appendLogEntry2(sourceScope.todosPath, sourceScope.id, sourceEntry);
|
|
15527
|
+
await appendLogEntry2(targetScope.todosPath, targetScope.id, targetEntry);
|
|
15528
|
+
console.log(`Moved [t:${id}] from ${sourceLabel} to ${targetLabel}`);
|
|
15529
|
+
}
|
|
15017
15530
|
|
|
15018
15531
|
// src/commands/backup.ts
|
|
15019
15532
|
init_config2();
|
|
@@ -15098,7 +15611,7 @@ import { Command as Command3 } from "commander";
|
|
|
15098
15611
|
// src/utils/doctor/index.ts
|
|
15099
15612
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
15100
15613
|
import { readFile as readFile27 } from "fs/promises";
|
|
15101
|
-
import { dirname as
|
|
15614
|
+
import { dirname as dirname13, join as join5 } from "path";
|
|
15102
15615
|
|
|
15103
15616
|
// src/utils/doctor/context.ts
|
|
15104
15617
|
init_config2();
|
|
@@ -15146,7 +15659,7 @@ init_paths();
|
|
|
15146
15659
|
import { resolve as resolve38, isAbsolute as isAbsolute5 } from "path";
|
|
15147
15660
|
import { readFile as readFile24, stat as stat4 } from "fs/promises";
|
|
15148
15661
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
15149
|
-
import { dirname as
|
|
15662
|
+
import { dirname as dirname12, join as join4 } from "path";
|
|
15150
15663
|
var CATEGORY = "env";
|
|
15151
15664
|
var syntaurRootExists = {
|
|
15152
15665
|
id: "env.syntaur-root-exists",
|
|
@@ -15477,14 +15990,14 @@ async function readLocalVersion() {
|
|
|
15477
15990
|
async function readLocalPkg() {
|
|
15478
15991
|
try {
|
|
15479
15992
|
const here = fileURLToPath6(import.meta.url);
|
|
15480
|
-
let dir =
|
|
15993
|
+
let dir = dirname12(here);
|
|
15481
15994
|
for (let i = 0; i < 6; i++) {
|
|
15482
15995
|
const candidate = join4(dir, "package.json");
|
|
15483
15996
|
try {
|
|
15484
15997
|
const text = await readFile24(candidate, "utf-8");
|
|
15485
15998
|
return JSON.parse(text);
|
|
15486
15999
|
} catch {
|
|
15487
|
-
dir =
|
|
16000
|
+
dir = dirname12(dir);
|
|
15488
16001
|
}
|
|
15489
16002
|
}
|
|
15490
16003
|
return null;
|
|
@@ -16885,14 +17398,14 @@ async function finalize(checks) {
|
|
|
16885
17398
|
async function readVersion() {
|
|
16886
17399
|
try {
|
|
16887
17400
|
const here = fileURLToPath7(import.meta.url);
|
|
16888
|
-
let dir =
|
|
17401
|
+
let dir = dirname13(here);
|
|
16889
17402
|
for (let i = 0; i < 6; i++) {
|
|
16890
17403
|
try {
|
|
16891
17404
|
const raw = await readFile27(join5(dir, "package.json"), "utf-8");
|
|
16892
17405
|
const parsed = JSON.parse(raw);
|
|
16893
17406
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
16894
17407
|
} catch {
|
|
16895
|
-
dir =
|
|
17408
|
+
dir = dirname13(dir);
|
|
16896
17409
|
}
|
|
16897
17410
|
}
|
|
16898
17411
|
return null;
|
|
@@ -17408,18 +17921,18 @@ init_paths();
|
|
|
17408
17921
|
init_fs();
|
|
17409
17922
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
17410
17923
|
import { readFile as readFile31 } from "fs/promises";
|
|
17411
|
-
import { dirname as
|
|
17924
|
+
import { dirname as dirname15, join as join7, resolve as resolve46 } from "path";
|
|
17412
17925
|
import { spawn as spawn4 } from "child_process";
|
|
17413
17926
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
17414
17927
|
|
|
17415
17928
|
// src/utils/version.ts
|
|
17416
17929
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
17417
17930
|
import { readFile as readFile30 } from "fs/promises";
|
|
17418
|
-
import { dirname as
|
|
17931
|
+
import { dirname as dirname14, join as join6 } from "path";
|
|
17419
17932
|
async function readPackageVersion(scriptUrl) {
|
|
17420
17933
|
try {
|
|
17421
17934
|
const scriptPath = fileURLToPath8(scriptUrl);
|
|
17422
|
-
const pkgRoot =
|
|
17935
|
+
const pkgRoot = dirname14(dirname14(scriptPath));
|
|
17423
17936
|
const raw = await readFile30(join6(pkgRoot, "package.json"), "utf-8");
|
|
17424
17937
|
const parsed = JSON.parse(raw);
|
|
17425
17938
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
@@ -17461,7 +17974,7 @@ async function writeState(state) {
|
|
|
17461
17974
|
`);
|
|
17462
17975
|
}
|
|
17463
17976
|
async function resolveNpmBin() {
|
|
17464
|
-
const nodeDir =
|
|
17977
|
+
const nodeDir = dirname15(process.execPath);
|
|
17465
17978
|
const isWin = process.platform === "win32";
|
|
17466
17979
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
17467
17980
|
const nearNode = join7(nodeDir, npmName);
|
|
@@ -17974,6 +18487,17 @@ program.command("disable-playbook").description("Disable a playbook so agents no
|
|
|
17974
18487
|
process.exit(1);
|
|
17975
18488
|
}
|
|
17976
18489
|
});
|
|
18490
|
+
program.command("regen-playbook-manifest").description("Rebuild ~/.syntaur/playbooks/manifest.md from current playbook files").action(async () => {
|
|
18491
|
+
try {
|
|
18492
|
+
await regenPlaybookManifestCommand();
|
|
18493
|
+
} catch (error) {
|
|
18494
|
+
console.error(
|
|
18495
|
+
"Error:",
|
|
18496
|
+
error instanceof Error ? error.message : String(error)
|
|
18497
|
+
);
|
|
18498
|
+
process.exit(1);
|
|
18499
|
+
}
|
|
18500
|
+
});
|
|
17977
18501
|
program.addCommand(todoCommand);
|
|
17978
18502
|
program.addCommand(backupCommand);
|
|
17979
18503
|
program.addCommand(doctorCommand);
|