@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.
- package/README.md +5 -2
- package/bin/cli.js +160 -7
- 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)",
|
|
@@ -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 \`
|
|
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.
|
|
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(`
|
|
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>
|
|
773
|
-
|
|
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.
|
|
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": [
|