@ksoftm/create-arc 1.0.5
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/LICENSE +23 -0
- package/README.md +56 -0
- package/bin/cli.js +326 -0
- package/package.json +48 -0
- package/templates/ARC-0000-maintenance.md +50 -0
- package/templates/ARC.md +150 -0
- package/templates/INDEX.md +25 -0
- package/templates/_TEMPLATE.md +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ksoftm (Kajalan)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
ARC is inspired by DOX (https://github.com/agent0ai/dox) by agent0ai, MIT licensed.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @ksoftm/create-arc
|
|
2
|
+
|
|
3
|
+
Scaffold and drive the **ARC framework** (Align → Refine → Construct) — plan-driven development for AI agents, in pure Markdown. Zero dependencies, any language, any agent.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @ksoftm/create-arc init # scaffold ARC into the current project
|
|
7
|
+
npx @ksoftm/create-arc new "Add per-key rate limit" # open and register a new unit of work
|
|
8
|
+
npx @ksoftm/create-arc status # every arc at a glance
|
|
9
|
+
npx @ksoftm/create-arc doctor # verify the registry is consistent
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What ARC is
|
|
13
|
+
|
|
14
|
+
Every unit of development is an **arc**: one Markdown file under `.arc/` that holds its whole story — the user's raw instructions (verbatim, append-only), the current plan, a refinement log, a task list, a worklog, and a status. The agent plans before it builds, refines the plan when the ask changes, and logs every edit — so any later session resumes from the arc, not the chat history. Full concept and protocol: **https://github.com/KsoftmHub/arc-framework-v1**.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Run on demand with `npx @ksoftm/create-arc …` (no install), or add it to a project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i -D @ksoftm/create-arc
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Node ≥ 18.
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
| Command | What it does |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `init [dir]` | Scaffold `ARC.md` + `.arc/` (index, template, standing maintenance arc, `notes/`, `archive/`). Idempotent — never overwrites. |
|
|
31
|
+
| `new "Title" [--dir=.] [--tags=a,b]` | Take the next sequential ID, create the arc from the template, and register its row in the index. |
|
|
32
|
+
| `status [dir] [--json]` | Print a table (or JSON) of every arc: ID, status, plan version, task progress, and which to resume. |
|
|
33
|
+
| `doctor [dir]` | Consistency checks — index ↔ file bijection, ID/`next_id` sanity, valid statuses. Exits non-zero on problems (CI-friendly). |
|
|
34
|
+
|
|
35
|
+
Common options: `--owner NAME` (defaults to `git config user.name`); `--tags a,b` on `new`; `--json` on `status`; `--version`, `--help`.
|
|
36
|
+
|
|
37
|
+
## What `init` produces
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
your-project/
|
|
41
|
+
├── ARC.md the protocol your AI agent reads
|
|
42
|
+
└── .arc/
|
|
43
|
+
├── INDEX.md registry of all arcs + next_id counter
|
|
44
|
+
├── _TEMPLATE.md the blank arc shape
|
|
45
|
+
├── ARC-0000-maintenance.md standing lane for trivial fixes
|
|
46
|
+
├── notes/ long research, linked from arcs
|
|
47
|
+
└── archive/ closed arcs (history is kept, never deleted)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Then what?
|
|
51
|
+
|
|
52
|
+
Give your AI agent development instructions as usual. With `ARC.md` in the repo — and the companion [ARC skill](https://github.com/KsoftmHub/arc-framework-v1) if you use Claude — the agent files each instruction into an arc verbatim, plans it, refines when you change your mind, builds, and logs progress.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT © Ksoftm (Kajalan)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-arc — scaffold and manage the ARC plan-driven development framework.
|
|
4
|
+
*
|
|
5
|
+
* npx @ksoftm/create-arc init [dir] [--owner=NAME]
|
|
6
|
+
* npx @ksoftm/create-arc new "Short imperative title" [--dir=.] [--tags=a,b] [--owner=NAME]
|
|
7
|
+
* npx @ksoftm/create-arc status [dir] [--json]
|
|
8
|
+
* npx @ksoftm/create-arc doctor [dir]
|
|
9
|
+
*
|
|
10
|
+
* Zero dependencies. Node >= 18. ARC protocol: see ARC.md after init.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import {
|
|
15
|
+
existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { basename, join, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const TEMPLATES = fileURLToPath(new URL("../templates/", import.meta.url));
|
|
21
|
+
|
|
22
|
+
// Read a UTF-8 file and normalize line endings to LF, so every downstream
|
|
23
|
+
// regex (frontmatter, fields, task markers) behaves identically regardless of
|
|
24
|
+
// whether the file was checked out with CRLF (Windows/Git autocrlf) or LF.
|
|
25
|
+
const readText = (p) => readFileSync(p, "utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
26
|
+
|
|
27
|
+
const PKG = JSON.parse(readText(new URL("../package.json", import.meta.url)));
|
|
28
|
+
|
|
29
|
+
const PLACEMENT = {
|
|
30
|
+
"ARC.md": "ARC.md",
|
|
31
|
+
"INDEX.md": ".arc/INDEX.md",
|
|
32
|
+
"_TEMPLATE.md": ".arc/_TEMPLATE.md",
|
|
33
|
+
"ARC-0000-maintenance.md": ".arc/ARC-0000-maintenance.md",
|
|
34
|
+
};
|
|
35
|
+
const DIRS = [".arc/notes", ".arc/archive"];
|
|
36
|
+
const VALID_STATUSES = new Set([
|
|
37
|
+
"draft", "planned", "refining", "in-progress", "blocked", "review", "done", "cancelled",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/* ----------------------------- small helpers ----------------------------- */
|
|
41
|
+
|
|
42
|
+
const today = () => new Date().toISOString().slice(0, 10);
|
|
43
|
+
|
|
44
|
+
const slugify = (title, max = 40) =>
|
|
45
|
+
(title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "")
|
|
46
|
+
.slice(0, max).replace(/-$/, "")) || "arc";
|
|
47
|
+
|
|
48
|
+
function detectOwner(dir) {
|
|
49
|
+
try {
|
|
50
|
+
const name = execFileSync("git", ["-C", dir, "config", "user.name"], {
|
|
51
|
+
encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"],
|
|
52
|
+
}).trim();
|
|
53
|
+
if (name) return name;
|
|
54
|
+
} catch { /* not a git repo / git absent */ }
|
|
55
|
+
return "user";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const BOOLEAN_FLAGS = new Set(["json", "help", "version"]);
|
|
59
|
+
|
|
60
|
+
function parseArgv(argv) {
|
|
61
|
+
const flags = {}; const pos = [];
|
|
62
|
+
for (let i = 0; i < argv.length; i++) {
|
|
63
|
+
const a = argv[i];
|
|
64
|
+
if (a.startsWith("--")) {
|
|
65
|
+
const eq = a.indexOf("=");
|
|
66
|
+
if (eq > -1) { flags[a.slice(2, eq)] = a.slice(eq + 1); continue; }
|
|
67
|
+
const key = a.slice(2);
|
|
68
|
+
const next = argv[i + 1];
|
|
69
|
+
if (!BOOLEAN_FLAGS.has(key) && next !== undefined && !next.startsWith("--")) {
|
|
70
|
+
flags[key] = next; i++; // --owner NAME form
|
|
71
|
+
} else flags[key] = true; // --json / trailing flag form
|
|
72
|
+
} else pos.push(a);
|
|
73
|
+
}
|
|
74
|
+
return { pos, flags };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const frontmatter = (text) => text.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
78
|
+
|
|
79
|
+
function field(front, key, fallback = "?") {
|
|
80
|
+
const m = front.match(new RegExp(`^${key}:\\s*(.*?)\\s*(?:#.*)?$`, "m"));
|
|
81
|
+
const v = m?.[1]?.trim();
|
|
82
|
+
return v || fallback;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const setField = (block, key, value) =>
|
|
86
|
+
block.replace(new RegExp(`^${key}:.*$`, "m"), `${key}: ${value}`);
|
|
87
|
+
|
|
88
|
+
function listArcFiles(arcDir) {
|
|
89
|
+
const ls = (d) => existsSync(d)
|
|
90
|
+
? readdirSync(d).filter((f) => /^ARC-\d+.*\.md$/.test(f)).sort().map((f) => join(d, f))
|
|
91
|
+
: [];
|
|
92
|
+
return { active: ls(arcDir), archived: ls(join(arcDir, "archive")) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* -------------------------------- commands ------------------------------- */
|
|
96
|
+
|
|
97
|
+
function cmdInit(target, flags) {
|
|
98
|
+
const dir = resolve(target ?? ".");
|
|
99
|
+
if (!existsSync(dir)) return fail(`target does not exist: ${dir}`);
|
|
100
|
+
const owner = flags.owner || detectOwner(dir);
|
|
101
|
+
const date = today();
|
|
102
|
+
let created = 0, skipped = 0;
|
|
103
|
+
|
|
104
|
+
for (const d of DIRS) {
|
|
105
|
+
mkdirSync(join(dir, d), { recursive: true });
|
|
106
|
+
const keep = join(dir, d, ".gitkeep");
|
|
107
|
+
if (!existsSync(keep)) writeFileSync(keep, "");
|
|
108
|
+
}
|
|
109
|
+
for (const [src, rel] of Object.entries(PLACEMENT)) {
|
|
110
|
+
const dest = join(dir, rel);
|
|
111
|
+
if (existsSync(dest)) { console.log(` skip ${rel} (exists)`); skipped++; continue; }
|
|
112
|
+
mkdirSync(join(dest, ".."), { recursive: true });
|
|
113
|
+
const text = readText(join(TEMPLATES, src))
|
|
114
|
+
.replaceAll("{{DATE}}", date).replaceAll("{{OWNER}}", owner);
|
|
115
|
+
writeFileSync(dest, text);
|
|
116
|
+
console.log(` ok ${rel}`);
|
|
117
|
+
created++;
|
|
118
|
+
}
|
|
119
|
+
console.log(`\nARC initialized at ${dir} (created ${created}, skipped ${skipped}, owner: ${owner})`);
|
|
120
|
+
if (created) console.log("Next: tell your agent to read ARC.md, or run `create-arc new \"Title\"`.");
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function cmdNew(title, flags) {
|
|
125
|
+
if (!title) return fail('usage: create-arc new "Short imperative title" [--dir=.] [--tags=a,b]');
|
|
126
|
+
const dir = resolve(flags.dir ?? ".");
|
|
127
|
+
const arcDir = join(dir, ".arc");
|
|
128
|
+
const indexPath = join(arcDir, "INDEX.md");
|
|
129
|
+
const templatePath = join(arcDir, "_TEMPLATE.md");
|
|
130
|
+
if (!existsSync(indexPath) || !existsSync(templatePath)) {
|
|
131
|
+
return fail(`.arc/INDEX.md or _TEMPLATE.md missing in ${dir} — run \`create-arc init\` first`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let index = readText(indexPath);
|
|
135
|
+
const m = index.match(/^next_id:\s*ARC-(\d+)\s*$/m);
|
|
136
|
+
if (!m) return fail("could not find 'next_id: ARC-NNNN' in INDEX.md");
|
|
137
|
+
const num = parseInt(m[1], 10);
|
|
138
|
+
const id = `ARC-${String(num).padStart(4, "0")}`;
|
|
139
|
+
const date = today();
|
|
140
|
+
const filename = `${id}-${slugify(title)}.md`;
|
|
141
|
+
const dest = join(arcDir, filename);
|
|
142
|
+
if (existsSync(dest)) return fail(`${dest} already exists`);
|
|
143
|
+
|
|
144
|
+
// build the arc file from the template
|
|
145
|
+
const tpl = readText(templatePath);
|
|
146
|
+
const fmMatch = tpl.match(/^---\n([\s\S]*?)\n---\n/);
|
|
147
|
+
if (!fmMatch) return fail("_TEMPLATE.md has no YAML frontmatter");
|
|
148
|
+
let front = fmMatch[1];
|
|
149
|
+
let body = tpl.slice(fmMatch[0].length);
|
|
150
|
+
|
|
151
|
+
front = setField(front, "id", id);
|
|
152
|
+
front = setField(front, "title", title);
|
|
153
|
+
front = setField(front, "status",
|
|
154
|
+
"draft # draft | planned | refining | in-progress | blocked | review | done | cancelled");
|
|
155
|
+
front = setField(front, "created", date);
|
|
156
|
+
front = setField(front, "updated", date);
|
|
157
|
+
if (flags.owner) front = setField(front, "owner", flags.owner);
|
|
158
|
+
if (flags.tags) {
|
|
159
|
+
const tags = String(flags.tags).split(",").map((t) => t.trim()).filter(Boolean).join(", ");
|
|
160
|
+
front = setField(front, "tags", `[${tags}]`);
|
|
161
|
+
}
|
|
162
|
+
body = body.replace("# ARC-0000 · <Title>", `# ${id} · ${title}`);
|
|
163
|
+
writeFileSync(dest, `---\n${front}\n---\n${body}`);
|
|
164
|
+
|
|
165
|
+
// update the index: bump next_id, insert registry row
|
|
166
|
+
index = index.replace(/^next_id:\s*ARC-\d+\s*$/m, `next_id: ARC-${String(num + 1).padStart(4, "0")}`);
|
|
167
|
+
const row = `| ${id} | ${title} | draft | 1 | ${date} | — | [${filename}](${filename}) |`;
|
|
168
|
+
const lines = index.split("\n");
|
|
169
|
+
let insertAt = -1;
|
|
170
|
+
for (let i = 0; i < lines.length; i++) {
|
|
171
|
+
if (lines[i].startsWith("## Archived")) break;
|
|
172
|
+
if (/^\|\s*ARC-/.test(lines[i])) insertAt = i;
|
|
173
|
+
}
|
|
174
|
+
if (insertAt === -1) return fail("could not locate the Active table in INDEX.md");
|
|
175
|
+
lines.splice(insertAt + 1, 0, row);
|
|
176
|
+
writeFileSync(indexPath, lines.join("\n"));
|
|
177
|
+
|
|
178
|
+
console.log(`created .arc/${filename}`);
|
|
179
|
+
console.log(`index row added, next_id -> ARC-${String(num + 1).padStart(4, "0")}`);
|
|
180
|
+
console.log("Now fill in: Raw Instruction (verbatim), Plan, Tasks.");
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function parseArc(path, archived) {
|
|
185
|
+
const text = readText(path);
|
|
186
|
+
const front = frontmatter(text);
|
|
187
|
+
const section = text.match(/^## 4 · Tasks\n([\s\S]*?)(?=^## |(?![\s\S]))/m)?.[1] ?? text;
|
|
188
|
+
const markers = [...section.matchAll(/^- \[([ >x!-])\]/gm)].map((x) => x[1]);
|
|
189
|
+
const count = (c) => markers.filter((x) => x === c).length;
|
|
190
|
+
return {
|
|
191
|
+
id: field(front, "id", basename(path).split("-").slice(0, 2).join("-")),
|
|
192
|
+
title: field(front, "title"),
|
|
193
|
+
status: field(front, "status"),
|
|
194
|
+
plan_version: field(front, "plan_version", "1"),
|
|
195
|
+
updated: field(front, "updated"),
|
|
196
|
+
tasks_done: count("x"),
|
|
197
|
+
tasks_total: markers.length,
|
|
198
|
+
tasks_in_progress: count(">"),
|
|
199
|
+
tasks_blocked: count("!"),
|
|
200
|
+
archived,
|
|
201
|
+
file: path,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function cmdStatus(target, flags) {
|
|
206
|
+
const dir = resolve(target ?? ".");
|
|
207
|
+
const arcDir = join(dir, ".arc");
|
|
208
|
+
if (!existsSync(arcDir)) return fail(`${arcDir} not found — run \`create-arc init\` first`);
|
|
209
|
+
|
|
210
|
+
const { active, archived } = listArcFiles(arcDir);
|
|
211
|
+
const arcs = [...active.map((p) => parseArc(p, false)), ...archived.map((p) => parseArc(p, true))];
|
|
212
|
+
|
|
213
|
+
if (flags.json) { console.log(JSON.stringify(arcs, null, 2)); return 0; }
|
|
214
|
+
if (!arcs.length) { console.log("no arcs found"); return 0; }
|
|
215
|
+
|
|
216
|
+
const indexText = existsSync(join(arcDir, "INDEX.md")) ? readText(join(arcDir, "INDEX.md")) : "";
|
|
217
|
+
const focus = indexText.match(/^active_focus:\s*(.+)$/m)?.[1]?.trim();
|
|
218
|
+
const nextId = indexText.match(/^next_id:\s*(.+)$/m)?.[1]?.trim();
|
|
219
|
+
|
|
220
|
+
const headers = ["ID", "STATUS", "V", "TASKS", "UPDATED", "TITLE"];
|
|
221
|
+
const rows = arcs.map((a) => [
|
|
222
|
+
a.id,
|
|
223
|
+
a.status + (a.archived ? " (archived)" : ""),
|
|
224
|
+
a.plan_version,
|
|
225
|
+
(a.tasks_total ? `${a.tasks_done}/${a.tasks_total}` : "—") + (a.tasks_blocked ? ` !${a.tasks_blocked}` : ""),
|
|
226
|
+
a.updated,
|
|
227
|
+
a.title,
|
|
228
|
+
]);
|
|
229
|
+
const w = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => String(r[i]).length)));
|
|
230
|
+
const fmt = (r) => r.map((c, i) => String(c).padEnd(w[i])).join(" ");
|
|
231
|
+
console.log(fmt(headers));
|
|
232
|
+
console.log("-".repeat(fmt(headers).length));
|
|
233
|
+
rows.forEach((r) => console.log(fmt(r)));
|
|
234
|
+
|
|
235
|
+
const activeCount = arcs.filter((a) => !a.archived).length;
|
|
236
|
+
const resume = arcs.filter((a) => !a.archived && (a.status === "in-progress" || a.status === "refining"))
|
|
237
|
+
.map((a) => a.id);
|
|
238
|
+
let summary = `\n${activeCount} active, ${arcs.length - activeCount} archived`;
|
|
239
|
+
if (focus) summary += ` · focus: ${focus}`;
|
|
240
|
+
if (nextId) summary += ` · next_id: ${nextId}`;
|
|
241
|
+
console.log(summary);
|
|
242
|
+
if (resume.length) console.log(`resume from: ${resume.join(", ")} (read Status Notes + last Worklog entry)`);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function cmdDoctor(target) {
|
|
247
|
+
const dir = resolve(target ?? ".");
|
|
248
|
+
const arcDir = join(dir, ".arc");
|
|
249
|
+
const indexPath = join(arcDir, "INDEX.md");
|
|
250
|
+
let failures = 0, warnings = 0;
|
|
251
|
+
const ok = (msg) => console.log(` OK ${msg}`);
|
|
252
|
+
const bad = (msg) => { console.log(` FAIL ${msg}`); failures++; };
|
|
253
|
+
const warn = (msg) => { console.log(` WARN ${msg}`); warnings++; };
|
|
254
|
+
|
|
255
|
+
if (!existsSync(arcDir)) return fail(`${arcDir} not found — run \`create-arc init\` first`);
|
|
256
|
+
if (!existsSync(indexPath)) { bad(".arc/INDEX.md missing"); return finish(); }
|
|
257
|
+
|
|
258
|
+
const index = readText(indexPath);
|
|
259
|
+
const { active, archived } = listArcFiles(arcDir);
|
|
260
|
+
const all = [...active.map((p) => ({ p, archived: false })), ...archived.map((p) => ({ p, archived: true }))];
|
|
261
|
+
|
|
262
|
+
// next_id sanity
|
|
263
|
+
const nm = index.match(/^next_id:\s*ARC-(\d+)\s*$/m);
|
|
264
|
+
const maxId = Math.max(-1, ...all.map(({ p }) => parseInt(basename(p).match(/^ARC-(\d+)/)?.[1] ?? "-1", 10)));
|
|
265
|
+
if (!nm) bad("next_id missing or malformed in INDEX.md");
|
|
266
|
+
else if (parseInt(nm[1], 10) <= maxId) bad(`next_id ARC-${nm[1]} is not greater than highest existing arc ARC-${String(maxId).padStart(4, "0")}`);
|
|
267
|
+
else ok(`next_id ARC-${nm[1]} > highest arc id`);
|
|
268
|
+
|
|
269
|
+
// file <-> index bijection, frontmatter integrity
|
|
270
|
+
const indexIds = new Set([...index.matchAll(/^\|\s*(ARC-\d{4})\s*\|/gm)].map((x) => x[1]));
|
|
271
|
+
for (const { p } of all) {
|
|
272
|
+
const fileId = basename(p).match(/^(ARC-\d{4})/)?.[1];
|
|
273
|
+
const front = frontmatter(readText(p));
|
|
274
|
+
const fmId = field(front, "id");
|
|
275
|
+
const status = field(front, "status");
|
|
276
|
+
if (fmId !== fileId) bad(`${basename(p)}: frontmatter id '${fmId}' != filename id '${fileId}'`);
|
|
277
|
+
if (!VALID_STATUSES.has(status)) bad(`${basename(p)}: invalid status '${status}'`);
|
|
278
|
+
if (!indexIds.has(fileId)) bad(`${basename(p)}: no row in INDEX.md`);
|
|
279
|
+
const inProg = [...readText(p).matchAll(/^- \[>\]/gm)].length;
|
|
280
|
+
if (inProg > 2) warn(`${basename(p)}: ${inProg} tasks marked [>] — keep at most 1–2 in progress`);
|
|
281
|
+
}
|
|
282
|
+
const fileIds = new Set(all.map(({ p }) => basename(p).match(/^(ARC-\d{4})/)?.[1]));
|
|
283
|
+
for (const id of indexIds) if (!fileIds.has(id)) bad(`INDEX.md row ${id} has no matching arc file`);
|
|
284
|
+
if (!failures) ok(`${all.length} arc file(s) ↔ INDEX rows consistent`);
|
|
285
|
+
|
|
286
|
+
return finish();
|
|
287
|
+
|
|
288
|
+
function finish() {
|
|
289
|
+
console.log(failures
|
|
290
|
+
? `\ndoctor: ${failures} problem(s), ${warnings} warning(s)`
|
|
291
|
+
: `\ndoctor: healthy (${warnings} warning(s))`);
|
|
292
|
+
return failures ? 1 : 0;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* --------------------------------- main ----------------------------------- */
|
|
297
|
+
|
|
298
|
+
function fail(msg) { console.error(`error: ${msg}`); return 1; }
|
|
299
|
+
|
|
300
|
+
function help() {
|
|
301
|
+
console.log(`create-arc v${PKG.version} — ARC plan-driven development (Align → Refine → Construct)
|
|
302
|
+
|
|
303
|
+
Usage:
|
|
304
|
+
create-arc init [dir] [--owner=NAME] scaffold ARC.md + .arc/ (idempotent)
|
|
305
|
+
create-arc new "Title" [--dir=.] [--tags=a,b] [--owner=NAME]
|
|
306
|
+
create + register the next arc
|
|
307
|
+
create-arc status [dir] [--json] status table across all arcs
|
|
308
|
+
create-arc doctor [dir] consistency checks (exit 1 on problems)
|
|
309
|
+
|
|
310
|
+
Protocol reference: ARC.md in your project root after init.`);
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const { pos, flags } = parseArgv(process.argv.slice(2));
|
|
315
|
+
const cmd = pos[0];
|
|
316
|
+
|
|
317
|
+
let code;
|
|
318
|
+
if (flags.version || cmd === "version") { console.log(PKG.version); code = 0; }
|
|
319
|
+
else if (!cmd || cmd === "help" || flags.help) code = help();
|
|
320
|
+
else if (cmd === "init") code = cmdInit(pos[1], flags);
|
|
321
|
+
else if (cmd === "new") code = cmdNew(pos.slice(1).join(" "), flags);
|
|
322
|
+
else if (cmd === "status") code = cmdStatus(pos[1], flags);
|
|
323
|
+
else if (cmd === "doctor") code = cmdDoctor(pos[1]);
|
|
324
|
+
else code = fail(`unknown command '${cmd}' — try: create-arc help`);
|
|
325
|
+
|
|
326
|
+
process.exit(code);
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ksoftm/create-arc",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"private": false,
|
|
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
|
+
"keywords": [
|
|
7
|
+
"arc",
|
|
8
|
+
"agents",
|
|
9
|
+
"agents-md",
|
|
10
|
+
"ai",
|
|
11
|
+
"ai-agents",
|
|
12
|
+
"plan-driven",
|
|
13
|
+
"planning",
|
|
14
|
+
"scaffold",
|
|
15
|
+
"claude",
|
|
16
|
+
"workflow"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Ksoftm (Kajalan)",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"create-arc": "bin/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"templates",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/KsoftmHub/arc-framework-v1.git",
|
|
39
|
+
"directory": "packages/create-arc"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/KsoftmHub/arc-framework-v1#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/KsoftmHub/arc-framework-v1/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ARC-0000
|
|
3
|
+
title: Maintenance (standing)
|
|
4
|
+
status: in-progress
|
|
5
|
+
created: {{DATE}}
|
|
6
|
+
updated: {{DATE}}
|
|
7
|
+
plan_version: 1
|
|
8
|
+
owner: {{OWNER}}
|
|
9
|
+
scope: ["*"]
|
|
10
|
+
depends_on: []
|
|
11
|
+
relates_to: []
|
|
12
|
+
tags: [maintenance]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# ARC-0000 · Maintenance (standing)
|
|
16
|
+
|
|
17
|
+
Perpetual arc for trivial changes: typo fixes, comment corrections, dependency
|
|
18
|
+
bumps with no behavior change, formatting. **Rule of thumb:** if it needs a
|
|
19
|
+
design decision, a plan, or more than a few minutes — it gets its own arc.
|
|
20
|
+
|
|
21
|
+
## 1 · Raw Instructions
|
|
22
|
+
|
|
23
|
+
### I1 — {{DATE}} (source: protocol)
|
|
24
|
+
> Standing lane defined by ARC.md §Intake-4 for trivial maintenance work.
|
|
25
|
+
|
|
26
|
+
## 2 · Plan (current — v1)
|
|
27
|
+
|
|
28
|
+
**Goal:** Trivial fixes stay traceable without per-fix arc overhead.
|
|
29
|
+
**Acceptance criteria:** every trivial change has a worklog entry here and an `[ARC-0000]` commit reference.
|
|
30
|
+
|
|
31
|
+
## 3 · Refinement Log
|
|
32
|
+
|
|
33
|
+
## 4 · Tasks
|
|
34
|
+
|
|
35
|
+
(none — this arc is worklog-driven)
|
|
36
|
+
|
|
37
|
+
## 5 · Worklog
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
### YYYY-MM-DD HH:MM — <what>
|
|
41
|
+
- read: <…>
|
|
42
|
+
- changed: <…>
|
|
43
|
+
- summary: <…>
|
|
44
|
+
-->
|
|
45
|
+
|
|
46
|
+
## 6 · Status Notes
|
|
47
|
+
|
|
48
|
+
**Current:** Open, perpetual.
|
|
49
|
+
**Blockers:** none
|
|
50
|
+
**Next:** —
|
package/templates/ARC.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# ARC framework
|
|
2
|
+
|
|
3
|
+
- ARC is a plan-driven development protocol installed in this project
|
|
4
|
+
- ARC = **Align → Refine → Construct**: capture the instruction, plan it, refine the plan when needed, only then build
|
|
5
|
+
- Every unit of development (feature, fix, refactor, integration, experiment) is an **arc** — one Markdown file in `.arc/` that holds its full story: raw instructions, plan, refinements, tasks, worklog, and status
|
|
6
|
+
- Any AI agent working in this repository must follow ARC across all development work
|
|
7
|
+
|
|
8
|
+
## Prime Directive
|
|
9
|
+
|
|
10
|
+
**No construction without an arc.**
|
|
11
|
+
Code, configs, schemas, infra, and docs are only changed under an arc that covers the change. If no arc covers it, create or extend one first (see Intake).
|
|
12
|
+
|
|
13
|
+
## Core Contract
|
|
14
|
+
|
|
15
|
+
- One arc file per unit of development; the arc is the single source of truth for that work's intent, plan, progress, and history
|
|
16
|
+
- Raw user instructions are **immutable and append-only** — never rewrite, summarize-in-place, or delete the user's words
|
|
17
|
+
- Plans evolve only through logged refinements, never by silent rewrite
|
|
18
|
+
- The codebase is truth for what currently *is*; arcs are truth for what was *asked*, what is *intended*, and what *happened*
|
|
19
|
+
- Every edit session ends with an Update After Editing pass — an edit without a worklog entry is an unfinished edit
|
|
20
|
+
|
|
21
|
+
## Layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
.arc/
|
|
25
|
+
├── INDEX.md # registry of all arcs: id, title, status, next_id counter
|
|
26
|
+
├── _TEMPLATE.md # copy this to start a new arc
|
|
27
|
+
├── ARC-0001-slug.md # active arcs live flat in .arc/
|
|
28
|
+
├── ARC-0002-slug.md
|
|
29
|
+
├── notes/ # optional: long research spilled out of an arc (linked back)
|
|
30
|
+
└── archive/ # done/cancelled arcs move here; INDEX row remains
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Arc filenames: `ARC-<NNNN>-<short-kebab-slug>.md`. IDs are sequential, never reused; take the next ID from `next_id` in INDEX.md and increment it.
|
|
34
|
+
|
|
35
|
+
## Lifecycle
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
draft ──► planned ──────────────► in-progress ──► review ──► done ──► (archive/)
|
|
39
|
+
▲ │
|
|
40
|
+
│ │ scope change / new instruction
|
|
41
|
+
└────── refining ◄─────┘
|
|
42
|
+
(plan_version + 1)
|
|
43
|
+
any state ──► blocked (with reason) ──► back to prior state
|
|
44
|
+
any state ──► cancelled ──► (archive/)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The two sanctioned flows:
|
|
48
|
+
|
|
49
|
+
1. **Fast lane** — plan → develop: `draft → planned → in-progress → done`
|
|
50
|
+
2. **Refine lane** — plan → refine/update → develop: `draft → planned → refining → planned (v2…vN) → in-progress → done`
|
|
51
|
+
|
|
52
|
+
Rules:
|
|
53
|
+
- `in-progress` requires an approved plan — explicit user approval, or implicit when the user clearly said to just do it
|
|
54
|
+
- A new instruction arriving mid-construction sends the arc to `refining`; resume construction only after the plan and tasks absorb it
|
|
55
|
+
- `done` requires all acceptance criteria checked and all tasks `[x]` or `[-]`
|
|
56
|
+
|
|
57
|
+
## Intake (Align)
|
|
58
|
+
|
|
59
|
+
When the user gives a development instruction:
|
|
60
|
+
|
|
61
|
+
1. Read `.arc/INDEX.md`
|
|
62
|
+
2. Decide:
|
|
63
|
+
- **Existing open arc covers it** → append the instruction verbatim to that arc's Raw Instructions, write a Refinement Log entry, update Plan and Tasks
|
|
64
|
+
- **Existing arc is done/archived** → create a new arc and link it via `relates_to`; do not reopen closed arcs
|
|
65
|
+
- **Nothing covers it** → copy `_TEMPLATE.md` to a new `ARC-NNNN-slug.md`, record the instruction verbatim as I1, draft the plan, register it in INDEX.md
|
|
66
|
+
3. Record the instruction **verbatim** — typos, voice-transcription noise, and all. If wording is ambiguous, add an `interpreted:` line beneath it stating your reading; if the interpretation changes scope or risk, confirm with the user before moving past `planned`
|
|
67
|
+
4. Trivial maintenance (typo-level, no design decision, ~minutes of work) may be logged as a worklog entry in the standing `ARC-0000-maintenance` arc instead of a new arc — but it is still logged
|
|
68
|
+
|
|
69
|
+
## Read Before Editing
|
|
70
|
+
|
|
71
|
+
Before touching any project file, in the current session:
|
|
72
|
+
|
|
73
|
+
1. Read `.arc/INDEX.md`
|
|
74
|
+
2. Open every arc whose `scope` covers the paths you expect to touch; read it fully — latest Raw Instructions, current Plan, open Tasks, and the last Worklog entries
|
|
75
|
+
3. Read the actual source files you will change as they exist now
|
|
76
|
+
4. If the project also has an AGENTS.md / DOX tree or similar, walk it too — arcs govern *what to build*; those docs govern *how to work in this codebase*
|
|
77
|
+
5. If no arc covers a non-trivial change, stop and run Intake first
|
|
78
|
+
|
|
79
|
+
Do not rely on memory from previous sessions or on the model's assumptions about file contents. Re-read the applicable arc chain and target files before editing.
|
|
80
|
+
|
|
81
|
+
## Update After Editing
|
|
82
|
+
|
|
83
|
+
Immediately after editing — same session, before reporting the task as done:
|
|
84
|
+
|
|
85
|
+
1. Advance task states in the arc's Tasks section
|
|
86
|
+
2. Append one Worklog entry: timestamp, tasks advanced, files read, files changed, summary, decisions made, follow-ups discovered
|
|
87
|
+
3. Update frontmatter: `updated` date and `status`; bump `plan_version` only if the Plan section changed
|
|
88
|
+
4. Sync the arc's row in `.arc/INDEX.md` (status, updated)
|
|
89
|
+
5. Reference the arc ID in the commit message: `[ARC-0007] add redis limiter`
|
|
90
|
+
6. On completion: check acceptance criteria, set `done`, move the file to `.arc/archive/`, keep the INDEX row pointing at the new path
|
|
91
|
+
|
|
92
|
+
## Refinement Rules
|
|
93
|
+
|
|
94
|
+
- Every new instruction on existing work becomes a numbered Raw Instruction entry (I2, I3, …) — verbatim, dated, with its source (chat, voice, issue, review)
|
|
95
|
+
- Each refinement gets a Refinement Log entry: new `plan_version`, date, triggering instruction, what changed in the plan, and its task impact (tasks added / modified / removed)
|
|
96
|
+
- The Plan section always shows only the **current** plan; history lives in the Refinement Log — never maintain two competing plans in one arc
|
|
97
|
+
- Removed scope is moved to "Out of scope" with a one-line reason, not silently deleted
|
|
98
|
+
|
|
99
|
+
## Status & Task Vocabulary
|
|
100
|
+
|
|
101
|
+
Statuses: `draft` · `planned` · `refining` · `in-progress` · `blocked` · `review` · `done` · `cancelled`
|
|
102
|
+
|
|
103
|
+
Task markers:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
- [ ] T1 pending
|
|
107
|
+
- [>] T2 in progress (at most one or two at a time)
|
|
108
|
+
- [x] T3 done
|
|
109
|
+
- [!] T4 blocked: <reason>
|
|
110
|
+
- [-] T5 cancelled: <reason>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Tasks are numbered `T1, T2, …` within an arc and never renumbered; new tasks append. Reference them everywhere as `ARC-0007/T3`.
|
|
114
|
+
|
|
115
|
+
## Conflict & Drift
|
|
116
|
+
|
|
117
|
+
- If an arc and the codebase disagree, the codebase is truth for the current state — record the drift in the Worklog, then fix the arc
|
|
118
|
+
- Newer raw instructions override older ones within an arc; note the override in the Refinement Log
|
|
119
|
+
- Arcs never weaken repository-wide rules, safety constraints, or verification requirements defined in AGENTS.md / DOX / CI
|
|
120
|
+
|
|
121
|
+
## Session Resume
|
|
122
|
+
|
|
123
|
+
Any agent, any tool, any time can resume work cold:
|
|
124
|
+
|
|
125
|
+
1. Read `.arc/INDEX.md` → find arcs in `in-progress`, `refining`, or `blocked`
|
|
126
|
+
2. Open each, read Status Notes and the last Worklog entry
|
|
127
|
+
3. Continue from the open `[>]`/`[ ]` tasks — after running Read Before Editing
|
|
128
|
+
|
|
129
|
+
This is the crash-recovery property: the arc, not the chat history, carries the state.
|
|
130
|
+
|
|
131
|
+
## Style
|
|
132
|
+
|
|
133
|
+
- Concise and operational; a plan is a contract, not an essay
|
|
134
|
+
- Acceptance criteria must be objectively checkable
|
|
135
|
+
- Keep an arc under ~300 lines; spill long research or option analysis to `.arc/notes/ARC-NNNN-topic.md` and link it
|
|
136
|
+
- Append-only sections (Raw Instructions, Refinement Log, Worklog) are never edited retroactively; everything else is kept current and stale text is removed
|
|
137
|
+
- Worklog entries record facts and decisions, not narration
|
|
138
|
+
|
|
139
|
+
## Coexistence
|
|
140
|
+
|
|
141
|
+
ARC composes with AGENTS.md, CLAUDE.md, DOX, and editor rule files. Division of labor: those describe the codebase and working conventions; ARC tracks intent, plans, and progress. If this file is referenced from an AGENTS.md, treat ARC as binding for all development work.
|
|
142
|
+
|
|
143
|
+
## Initialization
|
|
144
|
+
|
|
145
|
+
If `.arc/` does not exist yet, initialize ARC now before any other work:
|
|
146
|
+
|
|
147
|
+
1. Create `.arc/INDEX.md` with an empty registry and `next_id: ARC-0001`
|
|
148
|
+
2. Create `.arc/_TEMPLATE.md`, `.arc/notes/`, `.arc/archive/`
|
|
149
|
+
3. Create `ARC-0000-maintenance.md` as the standing trivial-fix arc (status: `in-progress`, perpetual)
|
|
150
|
+
4. If the user already gave development instructions, run Intake on them immediately
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ARC Index
|
|
2
|
+
|
|
3
|
+
next_id: ARC-0001
|
|
4
|
+
active_focus: —
|
|
5
|
+
|
|
6
|
+
## Active
|
|
7
|
+
|
|
8
|
+
| ID | Title | Status | Plan v | Updated | Depends | File |
|
|
9
|
+
|---|---|---|---|---|---|---|
|
|
10
|
+
| ARC-0000 | Maintenance (standing) | in-progress | 1 | {{DATE}} | — | [ARC-0000-maintenance.md](ARC-0000-maintenance.md) |
|
|
11
|
+
|
|
12
|
+
## Archived
|
|
13
|
+
|
|
14
|
+
| ID | Title | Outcome | Closed | File |
|
|
15
|
+
|---|---|---|---|---|
|
|
16
|
+
| — | — | — | — | — |
|
|
17
|
+
|
|
18
|
+
<!--
|
|
19
|
+
Rules:
|
|
20
|
+
- Take new IDs from next_id, then increment it — IDs are never reused.
|
|
21
|
+
- Every arc file in .arc/ and .arc/archive/ has exactly one row here.
|
|
22
|
+
- Sync a row's Status/Updated during every Update-After-Editing pass.
|
|
23
|
+
- When an arc closes, move its file to archive/ and move its row to Archived
|
|
24
|
+
(Outcome: done | cancelled).
|
|
25
|
+
-->
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ARC-0000
|
|
3
|
+
title: <short imperative title>
|
|
4
|
+
status: draft # draft | planned | refining | in-progress | blocked | review | done | cancelled
|
|
5
|
+
created: YYYY-MM-DD
|
|
6
|
+
updated: YYYY-MM-DD
|
|
7
|
+
plan_version: 1
|
|
8
|
+
owner: <user / team>
|
|
9
|
+
scope: [] # paths this arc owns or touches, e.g. [src/api/, prisma/schema.prisma]
|
|
10
|
+
depends_on: [] # arc IDs that must be done first, e.g. [ARC-0003]
|
|
11
|
+
relates_to: [] # related arc IDs (context, successors of closed arcs)
|
|
12
|
+
tags: [] # e.g. [api, infra, bugfix]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# ARC-0000 · <Title>
|
|
16
|
+
|
|
17
|
+
## 1 · Raw Instructions
|
|
18
|
+
<!-- Append-only. Verbatim user words — never edited, never deleted. -->
|
|
19
|
+
|
|
20
|
+
### I1 — YYYY-MM-DD (source: chat | voice | issue | review)
|
|
21
|
+
> <exact instruction text>
|
|
22
|
+
|
|
23
|
+
interpreted: <optional — your plain reading of an ambiguous/voice-noisy instruction; confirm with the user if it changes scope>
|
|
24
|
+
|
|
25
|
+
## 2 · Plan (current — v1)
|
|
26
|
+
<!-- Always reflects the CURRENT plan only. History lives in §3. -->
|
|
27
|
+
|
|
28
|
+
**Goal:** <one sentence — what exists when this arc is done>
|
|
29
|
+
|
|
30
|
+
**Approach:** <how, in a few lines — key technical decisions>
|
|
31
|
+
|
|
32
|
+
**In scope:**
|
|
33
|
+
- <…>
|
|
34
|
+
|
|
35
|
+
**Out of scope:**
|
|
36
|
+
- <…> — <reason>
|
|
37
|
+
|
|
38
|
+
**Acceptance criteria:** <!-- objectively checkable -->
|
|
39
|
+
- [ ] AC1 <…>
|
|
40
|
+
- [ ] AC2 <…>
|
|
41
|
+
|
|
42
|
+
**Affected paths:** <files/dirs expected to change — keep frontmatter `scope` in sync>
|
|
43
|
+
|
|
44
|
+
**Risks / unknowns:** <optional>
|
|
45
|
+
|
|
46
|
+
## 3 · Refinement Log
|
|
47
|
+
<!-- Append-only. One entry per plan version bump. Empty until the first refinement. -->
|
|
48
|
+
|
|
49
|
+
<!--
|
|
50
|
+
### v2 — YYYY-MM-DD — triggered by I2
|
|
51
|
+
- changed: <what changed in the plan and why>
|
|
52
|
+
- task impact: added T5–T6; modified T3; cancelled T4
|
|
53
|
+
-->
|
|
54
|
+
|
|
55
|
+
## 4 · Tasks
|
|
56
|
+
<!-- Numbered T1, T2, … — never renumber; append new tasks at the end.
|
|
57
|
+
Markers: [ ] pending · [>] in progress · [x] done · [!] blocked: reason · [-] cancelled: reason -->
|
|
58
|
+
|
|
59
|
+
- [ ] T1 <…>
|
|
60
|
+
- [ ] T2 <…>
|
|
61
|
+
- [ ] T3 <…>
|
|
62
|
+
|
|
63
|
+
## 5 · Worklog
|
|
64
|
+
<!-- Append-only. One entry per Update-After-Editing pass. Newest at the bottom. -->
|
|
65
|
+
|
|
66
|
+
<!--
|
|
67
|
+
### YYYY-MM-DD HH:MM — <one-line session note>
|
|
68
|
+
- tasks: T1 [>]→[x]; T2 →[>]
|
|
69
|
+
- read: <files read before editing>
|
|
70
|
+
- changed: <files created/modified/deleted>
|
|
71
|
+
- summary: <what was actually done>
|
|
72
|
+
- decisions: <choices made and why, if any>
|
|
73
|
+
- follow-ups: <new tasks, questions for the user, discovered debt>
|
|
74
|
+
-->
|
|
75
|
+
|
|
76
|
+
## 6 · Status Notes
|
|
77
|
+
<!-- Kept current — overwrite freely. The one place to glance for "where is this?" -->
|
|
78
|
+
|
|
79
|
+
**Current:** <one-line truth of where this stands>
|
|
80
|
+
**Blockers:** <none | …>
|
|
81
|
+
**Next:** <the very next action>
|