@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.
- package/README.md +5 -2
- package/bin/cli.js +145 -4
- 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
|
|
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
|
-
|
|
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>
|
|
773
|
-
|
|
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.
|
|
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": [
|