@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 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:** —
@@ -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>