@ksoftm/create-arc 1.2.0 → 1.4.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 (3) hide show
  1. package/README.md +5 -2
  2. package/bin/cli.js +160 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,10 +36,13 @@ Requires Node ≥ 18.
36
36
  | `start <arc>` | Set an arc to in-progress and log it. `<arc>` is an id or slug. |
37
37
  | `task <arc> <n> [done\|start\|block\|cancel\|pending]` | Toggle a task marker; `--add "text"` appends a new task. |
38
38
  | `block <arc> [--reason …]` | Set an arc to blocked, recording the reason in the worklog. |
39
+ | `refine <arc> "…"` | Fold a new instruction into an arc: append to §1, bump plan_version, log §3, set refining. |
40
+ | `note <arc> "…"` | Quick-append to §1 Raw Instructions (or §5 Worklog with `--worklog`). |
41
+ | `log <arc> [--json]` | Show the arc's worklog timeline. |
39
42
  | `done <arc>` | Mark done, log it, move the file to `archive/`, move its index row. |
40
43
  | `archive <arc> [--cancelled]` | Archive an arc (outcome done, or cancelled). |
41
- | `show <arc>` | Print one arc's plan, tasks, and status notes. |
42
- | `next` | Suggest what to work on next. |
44
+ | `show <arc> [--json]` | Print one arc's plan, tasks, and status notes. |
45
+ | `next [--json]` | Suggest what to work on next. |
43
46
  | `status [dir] [--json]` | Print a table (or JSON) of every arc: ID, status, plan version, task progress, and which to resume. |
44
47
  | `doctor [dir] [--fix]` | Consistency checks — index ↔ file bijection, ID/`next_id` sanity, valid statuses. Exits non-zero on problems (CI-friendly). `--fix` auto-repairs drift. |
45
48
 
package/bin/cli.js CHANGED
@@ -55,7 +55,7 @@ function detectOwner(dir) {
55
55
  return "user";
56
56
  }
57
57
 
58
- const BOOLEAN_FLAGS = new Set(["json", "help", "version", "fix", "force", "cancelled", "cancel"]);
58
+ const BOOLEAN_FLAGS = new Set(["json", "help", "version", "fix", "force", "cancelled", "cancel", "worklog"]);
59
59
  const REPEATABLE_FLAGS = new Set(["task"]);
60
60
 
61
61
  function parseArgv(argv) {
@@ -212,6 +212,60 @@ function planVersionOf(arcPath) {
212
212
  return parseInt(field(frontmatter(readText(arcPath)), "plan_version", "1"), 10) || 1;
213
213
  }
214
214
 
215
+ // Count existing instructions (I1, I2, …) in §1 to compute the next index.
216
+ function nextInstructionIndex(text) {
217
+ const nums = [...text.matchAll(/^### I(\d+)\b/gm)].map((m) => parseInt(m[1], 10));
218
+ return (nums.length ? Math.max(...nums) : 0) + 1;
219
+ }
220
+
221
+ // Append a verbatim instruction to "## 1 · Raw Instructions". Returns the new I-index.
222
+ function appendInstruction(arcPath, instruction, source = "chat") {
223
+ let text = readText(arcPath);
224
+ const idx = nextInstructionIndex(text);
225
+ const quoted = String(instruction).trim().split("\n").map((l) => `> ${l}`).join("\n");
226
+ const entry = `\n### I${idx} — ${today()} (source: ${source})\n${quoted}\n`;
227
+ const sec = text.match(/(^## 1 · Raw Instructions\n)([\s\S]*?)(?=^## )/m);
228
+ if (!sec) { writeFileSync(arcPath, text + entry); return idx; }
229
+ // append at the end of §1, before the next "## "
230
+ const insertAt = sec.index + sec[1].length + sec[2].replace(/\n*$/, "\n").length;
231
+ const head = text.slice(0, sec.index + sec[1].length);
232
+ const bodyTrimmed = sec[2].replace(/\n+$/, "\n");
233
+ text = head + bodyTrimmed + entry + "\n" + text.slice(sec.index + sec[0].length);
234
+ writeFileSync(arcPath, text);
235
+ return idx;
236
+ }
237
+
238
+ // Append a Refinement Log entry under "## 3 · Refinement Log" for a version bump.
239
+ function appendRefinement(arcPath, version, changed, instructionIdx) {
240
+ let text = readText(arcPath);
241
+ const trigger = instructionIdx ? ` — triggered by I${instructionIdx}` : "";
242
+ const entry = `\n### v${version} — ${today()}${trigger}\n- changed: ${changed}\n`;
243
+ const m = text.match(/^## 3 · Refinement Log\n/m);
244
+ if (!m) { writeFileSync(arcPath, text + entry); return; }
245
+ // insert right after the heading + its leading HTML comment (the example block)
246
+ let afterHead = m.index + m[0].length;
247
+ const rest = text.slice(afterHead);
248
+ const lead = rest.match(/^(\s*<!--[\s\S]*?-->\n)/); // skip the template's example comment
249
+ if (lead) afterHead += lead[0].length;
250
+ text = text.slice(0, afterHead) + entry + text.slice(afterHead);
251
+ writeFileSync(arcPath, text);
252
+ }
253
+
254
+ // Extract worklog entries (### timestamp — note + bullet lines) from §5.
255
+ function readWorklog(arcPath) {
256
+ const text = readText(arcPath);
257
+ const sec = text.match(/^## 5 · Worklog\n([\s\S]*?)(?=^## |\Z)/m)?.[1] ?? "";
258
+ const body = sec.replace(/<!--[\s\S]*?-->/g, "").trim();
259
+ if (!body) return [];
260
+ // split on entry headers, keep the header text
261
+ const parts = body.split(/(?=^### )/m).map((s) => s.trim()).filter(Boolean);
262
+ return parts.map((p) => {
263
+ const head = p.match(/^### (.+)/)?.[1] ?? "";
264
+ const lines = p.split("\n").slice(1).map((l) => l.trim()).filter(Boolean);
265
+ return { head, lines };
266
+ });
267
+ }
268
+
215
269
  /* -------------------------------- commands ------------------------------- */
216
270
 
217
271
  function cmdInit(target, flags) {
@@ -583,6 +637,61 @@ function cmdTask(ref, flags, rest) {
583
637
  return 0;
584
638
  }
585
639
 
640
+ // arc refine <arc> "instruction" [--changed "…"] [--source chat|voice|issue|review]
641
+ // Appends the instruction verbatim to §1, bumps plan_version, logs §3, sets refining.
642
+ function cmdRefine(ref, flags, rest) {
643
+ const r = resolveForMutation(ref, flags);
644
+ if (r.err) return r.err;
645
+ if (r.archived) return fail(`${r.id} is archived — restore it before refining`);
646
+ const instruction = (rest || []).join(" ").trim() || (typeof flags.note === "string" ? flags.note : "");
647
+ if (!instruction) return fail('what changed? usage: arc refine <arc> "the new instruction" [--changed "plan delta"]');
648
+
649
+ const iIdx = appendInstruction(r.path, instruction, flags.source || "chat");
650
+ const nextVer = planVersionOf(r.path) + 1;
651
+ const changed = (typeof flags.changed === "string" && flags.changed) || instruction;
652
+ appendRefinement(r.path, nextVer, changed, iIdx);
653
+ updateArcFrontmatter(r.path, { plan_version: nextVer, status: "refining" });
654
+ syncIndexRow(r.arcDir, r.id, { status: "refining", planVersion: nextVer });
655
+ appendWorklog(r.path, `refined to plan v${nextVer} (I${iIdx})`);
656
+ console.log(`${r.id} → refining · plan v${nextVer} · recorded I${iIdx}`);
657
+ console.log(`Next: update §2 Plan to reflect v${nextVer}, then adjust Tasks.`);
658
+ return 0;
659
+ }
660
+
661
+ // arc note <arc> "text" [--worklog] — quick-append an instruction (default) or a worklog note.
662
+ function cmdNote(ref, flags, rest) {
663
+ const r = resolveForMutation(ref, flags);
664
+ if (r.err) return r.err;
665
+ const note = (rest || []).join(" ").trim() || (typeof flags.note === "string" ? flags.note : "");
666
+ if (!note) return fail('usage: arc note <arc> "text" [--worklog]');
667
+ if (flags.worklog) {
668
+ appendWorklog(r.path, note);
669
+ updateArcFrontmatter(r.path, {});
670
+ console.log(`${r.id}: worklog note added`);
671
+ } else {
672
+ const iIdx = appendInstruction(r.path, note, flags.source || "chat");
673
+ updateArcFrontmatter(r.path, {});
674
+ console.log(`${r.id}: recorded I${iIdx} in Raw Instructions`);
675
+ }
676
+ return 0;
677
+ }
678
+
679
+ // arc log <arc> [--json] — show an arc's worklog timeline.
680
+ function cmdLog(ref, flags) {
681
+ const r = resolveForMutation(ref, flags);
682
+ if (r.err) return r.err;
683
+ const entries = readWorklog(r.path);
684
+ const a = parseArc(r.path, r.archived);
685
+ if (flags.json) { console.log(JSON.stringify({ id: a.id, title: a.title, worklog: entries }, null, 2)); return 0; }
686
+ console.log(`${a.id} · ${a.title} — worklog (${entries.length} entr${entries.length === 1 ? "y" : "ies"})`);
687
+ if (!entries.length) { console.log(" (no worklog entries yet)"); return 0; }
688
+ for (const e of entries) {
689
+ console.log(`\n• ${e.head}`);
690
+ for (const l of e.lines) console.log(` ${l}`);
691
+ }
692
+ return 0;
693
+ }
694
+
586
695
  // arc show <ref> — print one arc's plan, tasks, and status notes.
587
696
  function cmdShow(ref, flags) {
588
697
  const r = resolveForMutation(ref, flags);
@@ -590,6 +699,16 @@ function cmdShow(ref, flags) {
590
699
  const text = readText(r.path);
591
700
  const a = parseArc(r.path, r.archived);
592
701
  const sec = (n, title) => text.match(new RegExp(`^## ${n} · ${title}\\n([\\s\\S]*?)(?=^## |\\Z)`, "m"))?.[1]?.trim() ?? "";
702
+ if (flags.json) {
703
+ const clean = (s) => s.replace(/<!--[\s\S]*?-->/g, "").trim();
704
+ console.log(JSON.stringify({
705
+ ...a,
706
+ plan: clean(sec(2, "Plan \\(current[^)]*\\)") || sec(2, "Plan.*")),
707
+ tasks: clean(sec(4, "Tasks")),
708
+ status_notes: clean(sec(6, "Status Notes")),
709
+ }, null, 2));
710
+ return 0;
711
+ }
593
712
  console.log(`${a.id} · ${a.title}`);
594
713
  console.log(`status: ${a.status}${r.archived ? " (archived)" : ""} · plan v${a.plan_version} · tasks ${a.tasks_total ? `${a.tasks_done}/${a.tasks_total}` : "—"} · updated ${a.updated}`);
595
714
  const plan = sec(2, "Plan \\(current[^)]*\\)") || sec(2, "Plan.*");
@@ -622,6 +741,15 @@ function cmdNext(flags) {
622
741
  byStatus("in-progress")[0] || byStatus("refining")[0] ||
623
742
  byStatus("planned")[0] || byStatus("review")[0] || byStatus("draft")[0];
624
743
 
744
+ if (flags.json) {
745
+ console.log(JSON.stringify({
746
+ next: pick ? { id: pick.id, title: pick.title, status: pick.status,
747
+ tasks_done: pick.tasks_done, tasks_total: pick.tasks_total,
748
+ tasks_blocked: pick.tasks_blocked } : null,
749
+ blocked: byStatus("blocked").map((a) => a.id),
750
+ }, null, 2));
751
+ return 0;
752
+ }
625
753
  if (!pick) { console.log("nothing actionable — all arcs are blocked or done"); return 0; }
626
754
  console.log(`next: ${pick.id} · ${pick.title} [${pick.status}]`);
627
755
  console.log(` tasks ${pick.tasks_total ? `${pick.tasks_done}/${pick.tasks_total}` : "—"}${pick.tasks_blocked ? ` · ${pick.tasks_blocked} blocked` : ""}`);
@@ -652,7 +780,7 @@ Steps: run \`arc status\` to see existing arcs; if an open arc already covers th
652
780
 
653
781
  "$ARGUMENTS"
654
782
 
655
- Append it verbatim as the next Raw Instruction, add a Refinement Log entry (new plan_version, what changed, task impact), rewrite the Plan to reflect only the current intent, and adjust the Tasks. Move dropped scope to "Out of scope". Never edit the append-only sections retroactively.`,
783
+ Run \`arc refine <arc> "$ARGUMENTS" --changed "<one-line plan delta>"\` — this appends the instruction verbatim to §1, bumps plan_version, adds a §3 Refinement Log entry, and sets the arc to refining. Then rewrite §2 Plan to reflect only the current intent, adjust §4 Tasks (move dropped scope to "Out of scope"), and resume construction only once the plan and tasks absorb the change. Never edit the append-only sections retroactively.`,
656
784
  },
657
785
  "arc-build": {
658
786
  desc: "ARC: do the work for the active arc (Read Before / Update After Editing)",
@@ -662,11 +790,23 @@ Focus: $ARGUMENTS`,
662
790
  },
663
791
  "arc-status": {
664
792
  desc: "ARC: summarize all arcs and what to resume",
665
- body: `Run \`npx @ksoftm/create-arc status\` (or read .arc/INDEX.md and each arc). Report every arc's id, status, plan version, task progress, and which in-progress/refining arcs to resume reading each one's Status Notes and last Worklog entry.`,
793
+ body: `Run \`arc status\` (or read .arc/INDEX.md). Report every arc's id, status, plan version, and task progress, and call out which in-progress/refining arcs to resume. For any arc that needs detail, run \`arc show <arc>\` for its plan/tasks/status and \`arc log <arc>\` for its worklog history. Then run \`arc next\` to recommend what to pick up.`,
666
794
  },
667
795
  "arc-resume": {
668
796
  desc: "ARC: pick up the in-progress arc cold",
669
- body: `Read ./ARC.md. Read .arc/INDEX.md, find arcs in in-progress or refining, open them, read Status Notes and the last Worklog entry, then continue from the open tasks — after running Read Before Editing. If code and the arc disagree, the code is truth: note the drift in the Worklog and correct the arc.`,
797
+ body: `Read ./ARC.md. Run \`arc next\` to find what to work on, then \`arc show <arc>\` and \`arc log <arc>\` to read its plan, status notes, and last worklog entries. Continue from the open tasks — after running Read Before Editing. If code and the arc disagree, the code is truth: \`arc note <arc> "drift: …" --worklog\` to record it, then correct the arc.`,
798
+ },
799
+ "arc-note": {
800
+ desc: "ARC: quick-capture an instruction or worklog note onto an arc",
801
+ body: `Capture this onto the relevant arc without rewriting the plan:
802
+
803
+ "$ARGUMENTS"
804
+
805
+ If it's a new requirement or instruction, run \`arc note <arc> "$ARGUMENTS"\` (it lands verbatim in §1 Raw Instructions). If it's a progress/decision note about work just done, run \`arc note <arc> "$ARGUMENTS" --worklog\` instead. Use \`arc status\` first if you're unsure which arc this belongs to. If it actually changes the plan or scope, use /arc-refine instead of /arc-note.`,
806
+ },
807
+ "arc-log": {
808
+ desc: "ARC: show an arc's worklog history",
809
+ body: `Run \`arc log <arc>\` for the arc referenced by "$ARGUMENTS" (resolve it via \`arc status\` if only a topic is given) and summarize its worklog timeline: what was done, in what order, and any decisions or follow-ups recorded. Then state where the arc currently stands and what the next step is.`,
670
810
  },
671
811
  };
672
812
 
@@ -709,7 +849,7 @@ function cmdAgentInit(flags) {
709
849
  console.log(` ${agent.padEnd(9)} ${rel}/ (${Object.keys(ARC_COMMANDS).length} commands)`);
710
850
  }
711
851
  console.log(`\nAgent commands written (created ${created}, skipped ${skipped}${flags.force ? "" : "; use --force to overwrite"}).`);
712
- console.log(`Type /arc-new, /arc-build, /arc-status, /arc-refine, /arc-resume in your agent.`);
852
+ console.log(`Commands: /arc-new /arc-build /arc-refine /arc-note /arc-log /arc-status /arc-resume`);
713
853
  return 0;
714
854
  }
715
855
 
@@ -730,6 +870,13 @@ const COMMAND_HELP = {
730
870
  Mark an arc done, log it, move the file to .arc/archive/, and move its INDEX row to Archived.`,
731
871
  block: `arc block <arc> [--reason "…"]
732
872
  Set an arc to blocked, recording the reason in the worklog.`,
873
+ refine: `arc refine <arc> "the new instruction" [--changed "plan delta"] [--source chat|voice|issue|review]
874
+ Fold a new instruction into an arc: append it verbatim to §1, bump plan_version,
875
+ add a §3 Refinement Log entry, and set status to refining.`,
876
+ note: `arc note <arc> "text" [--worklog] [--source …]
877
+ Quick-append a note. Default goes to §1 Raw Instructions; --worklog appends to §5 Worklog.`,
878
+ log: `arc log <arc> [--json]
879
+ Show an arc's worklog timeline (newest entries as recorded).`,
733
880
  archive: `arc archive <arc> [--cancelled] [--reason "…"]
734
881
  Archive an arc. Default outcome is "done"; --cancelled archives it as cancelled.`,
735
882
  task: `arc task <arc> <n> [done|start|block|cancel|pending]
@@ -763,14 +910,17 @@ Capture & plan
763
910
  Work the arc
764
911
  start <arc> → in-progress
765
912
  task <arc> <n> [action] tick a task ([done]/start/block/cancel/pending); --add "text"
913
+ refine <arc> "…" fold in a new instruction (bumps plan version → refining)
914
+ note <arc> "…" quick-append to Raw Instructions; --worklog for a worklog note
766
915
  block <arc> [--reason …] → blocked
767
916
  done <arc> → done + archive (file + index row)
768
917
  archive <arc> [--cancelled]
769
918
 
770
919
  Inspect
771
920
  status [dir] [--json] table of every arc
772
- show <arc> one arc's plan, tasks, status
773
- next what to work on next
921
+ show <arc> [--json] one arc's plan, tasks, status
922
+ log <arc> [--json] an arc's worklog timeline
923
+ next [--json] what to work on next
774
924
  doctor [dir] [--fix] consistency checks (+ auto-repair)
775
925
 
776
926
  <arc> is an id or slug: ARC-0007, 7, or a slug substring like "rate-limit".
@@ -800,6 +950,9 @@ else if (cmd === "start") code = cmdStart(pos[1], flags);
800
950
  else if (cmd === "done") code = cmdDone(pos[1], flags);
801
951
  else if (cmd === "block") code = cmdBlock(pos[1], flags);
802
952
  else if (cmd === "review") code = cmdReview(pos[1], flags);
953
+ else if (cmd === "refine") code = cmdRefine(pos[1], flags, pos.slice(2));
954
+ else if (cmd === "note") code = cmdNote(pos[1], flags, pos.slice(2));
955
+ else if (cmd === "log") code = cmdLog(pos[1], flags);
803
956
  else if (cmd === "archive") code = cmdArchive(pos[1], flags);
804
957
  else if (cmd === "task") code = cmdTask(pos[1], flags, pos.slice(2));
805
958
  else if (cmd === "show" || cmd === "view") code = cmdShow(pos[1], flags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksoftm/create-arc",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "private": false,
5
5
  "description": "Scaffold and manage ARC — plan-driven development for AI agents (Align → Refine → Construct). Pure-Markdown plans, tasks, worklogs and statuses in .arc/, for any language and any agent.",
6
6
  "keywords": [