@ksoftm/create-arc 1.2.0 → 1.3.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 +145 -4
  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)",
@@ -730,6 +858,13 @@ const COMMAND_HELP = {
730
858
  Mark an arc done, log it, move the file to .arc/archive/, and move its INDEX row to Archived.`,
731
859
  block: `arc block <arc> [--reason "…"]
732
860
  Set an arc to blocked, recording the reason in the worklog.`,
861
+ refine: `arc refine <arc> "the new instruction" [--changed "plan delta"] [--source chat|voice|issue|review]
862
+ Fold a new instruction into an arc: append it verbatim to §1, bump plan_version,
863
+ add a §3 Refinement Log entry, and set status to refining.`,
864
+ note: `arc note <arc> "text" [--worklog] [--source …]
865
+ Quick-append a note. Default goes to §1 Raw Instructions; --worklog appends to §5 Worklog.`,
866
+ log: `arc log <arc> [--json]
867
+ Show an arc's worklog timeline (newest entries as recorded).`,
733
868
  archive: `arc archive <arc> [--cancelled] [--reason "…"]
734
869
  Archive an arc. Default outcome is "done"; --cancelled archives it as cancelled.`,
735
870
  task: `arc task <arc> <n> [done|start|block|cancel|pending]
@@ -763,14 +898,17 @@ Capture & plan
763
898
  Work the arc
764
899
  start <arc> → in-progress
765
900
  task <arc> <n> [action] tick a task ([done]/start/block/cancel/pending); --add "text"
901
+ refine <arc> "…" fold in a new instruction (bumps plan version → refining)
902
+ note <arc> "…" quick-append to Raw Instructions; --worklog for a worklog note
766
903
  block <arc> [--reason …] → blocked
767
904
  done <arc> → done + archive (file + index row)
768
905
  archive <arc> [--cancelled]
769
906
 
770
907
  Inspect
771
908
  status [dir] [--json] table of every arc
772
- show <arc> one arc's plan, tasks, status
773
- next what to work on next
909
+ show <arc> [--json] one arc's plan, tasks, status
910
+ log <arc> [--json] an arc's worklog timeline
911
+ next [--json] what to work on next
774
912
  doctor [dir] [--fix] consistency checks (+ auto-repair)
775
913
 
776
914
  <arc> is an id or slug: ARC-0007, 7, or a slug substring like "rate-limit".
@@ -800,6 +938,9 @@ else if (cmd === "start") code = cmdStart(pos[1], flags);
800
938
  else if (cmd === "done") code = cmdDone(pos[1], flags);
801
939
  else if (cmd === "block") code = cmdBlock(pos[1], flags);
802
940
  else if (cmd === "review") code = cmdReview(pos[1], flags);
941
+ else if (cmd === "refine") code = cmdRefine(pos[1], flags, pos.slice(2));
942
+ else if (cmd === "note") code = cmdNote(pos[1], flags, pos.slice(2));
943
+ else if (cmd === "log") code = cmdLog(pos[1], flags);
803
944
  else if (cmd === "archive") code = cmdArchive(pos[1], flags);
804
945
  else if (cmd === "task") code = cmdTask(pos[1], flags, pos.slice(2));
805
946
  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.3.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": [