code-anchored-context 0.1.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/.agents/skills/README.md +13 -0
- package/.agents/skills/development-initiative-context/SKILL.md +209 -0
- package/AGENTS.md +52 -0
- package/Development/AGENTS.md +46 -0
- package/Development/README.md +259 -0
- package/Development/_templates/backlog-item.md +40 -0
- package/Development/_templates/initiative/README.md +59 -0
- package/Development/_templates/initiative/architecture.md +44 -0
- package/Development/_templates/initiative/backlog.md +28 -0
- package/Development/_templates/initiative/brief.html +56 -0
- package/Development/_templates/initiative/decisions/ADR-0000-template.md +34 -0
- package/Development/_templates/initiative/delivery.md +38 -0
- package/Development/_templates/initiative/infrastructure.md +33 -0
- package/Development/_templates/initiative/interface.md +41 -0
- package/Development/_templates/initiative/operations.md +40 -0
- package/Development/_templates/initiative/plan.md +57 -0
- package/Development/_templates/initiative/release-doc-notes.md +35 -0
- package/Development/_templates/initiative/spec.md +35 -0
- package/Development/_templates/initiative/testing.md +41 -0
- package/Development/_templates/planned-initiative/README.md +55 -0
- package/Development/_templates/planned-initiative/architecture.md +44 -0
- package/Development/_templates/planned-initiative/backlog.md +27 -0
- package/Development/_templates/planned-initiative/decisions/ADR-0000-template.md +35 -0
- package/Development/_templates/planned-initiative/delivery.md +33 -0
- package/Development/_templates/planned-initiative/infrastructure.md +30 -0
- package/Development/_templates/planned-initiative/interface.md +41 -0
- package/Development/_templates/planned-initiative/operations.md +41 -0
- package/Development/_templates/planned-initiative/plan.md +40 -0
- package/Development/_templates/planned-initiative/release-doc-notes.md +28 -0
- package/Development/_templates/planned-initiative/spec.md +37 -0
- package/Development/_templates/planned-initiative/testing.md +30 -0
- package/Development/_templates/program/README.md +63 -0
- package/Development/_templates/program/backlog.md +26 -0
- package/Development/_templates/program/context.md +25 -0
- package/Development/_templates/program/decisions/ADR-0000-template.md +34 -0
- package/Development/_templates/program/planned-initiatives/.gitkeep +1 -0
- package/Development/_templates/program/releases/v0_1_0.md +32 -0
- package/Development/_templates/program/roadmap.md +27 -0
- package/Development/_templates/release-context/README.md +33 -0
- package/Development/_templates/release-context/backlog.md +14 -0
- package/Development/_templates/release-context/initiatives/.gitkeep +1 -0
- package/Development/_templates/release-transition.md +39 -0
- package/Development/backlog/README.md +17 -0
- package/Development/backlog/items/.gitkeep +0 -0
- package/Development/code-anchored-context-structure.md +132 -0
- package/Development/code-anchored-context-why.md +80 -0
- package/Development/code-anchored-context.html +830 -0
- package/Development/current.md +32 -0
- package/Development/giving-ai-agents-context-around-code.md +496 -0
- package/Development/programs/README.md +27 -0
- package/Development/releases/v0_1_0/README.md +33 -0
- package/Development/releases/v0_1_0/backlog.md +15 -0
- package/Development/releases/v0_1_0/initiatives/.gitkeep +1 -0
- package/Development/terminology.md +120 -0
- package/Documentation/.order +2 -0
- package/Documentation/Welcome.md +43 -0
- package/Documentation/_authoring/README.md +45 -0
- package/Documentation/_authoring/areas/README.md +16 -0
- package/Documentation/_authoring/areas/_template.md +51 -0
- package/Documentation/_authoring/terminology.md +40 -0
- package/Documentation/_authoring/workflow.md +123 -0
- package/Documentation/_templates/area/README.md +20 -0
- package/Documentation/_templates/area/features/feature-template.md +29 -0
- package/Documentation/releases/index.md +18 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/bin/code-anchored-context.js +558 -0
- package/package.json +45 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execPath } from 'node:process';
|
|
4
|
+
import { constants as fsConstants } from 'node:fs';
|
|
5
|
+
import {
|
|
6
|
+
access,
|
|
7
|
+
mkdir,
|
|
8
|
+
readFile,
|
|
9
|
+
readdir,
|
|
10
|
+
rename,
|
|
11
|
+
rm,
|
|
12
|
+
stat,
|
|
13
|
+
writeFile
|
|
14
|
+
} from 'node:fs/promises';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const defaultRelease = 'v0_1_0';
|
|
20
|
+
const defaultReleaseLabel = 'v0.1.0';
|
|
21
|
+
const skillName = 'development-initiative-context';
|
|
22
|
+
const agentSectionStart = '<!-- code-anchored-context:start -->';
|
|
23
|
+
const agentSectionEnd = '<!-- code-anchored-context:end -->';
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const args = parseArgs(process.argv.slice(2));
|
|
27
|
+
|
|
28
|
+
if (args.help) {
|
|
29
|
+
printHelp();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (args.version) {
|
|
34
|
+
const packageJson = JSON.parse(
|
|
35
|
+
await readFile(path.join(packageRoot, 'package.json'), 'utf8')
|
|
36
|
+
);
|
|
37
|
+
console.log(packageJson.version);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (args.command !== 'init') {
|
|
42
|
+
throw new Error(`Unknown command "${args.command}". Run "code-anchored-context --help".`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
validateRelease(args.release);
|
|
46
|
+
|
|
47
|
+
const targetRoot = path.resolve(args.target);
|
|
48
|
+
const projectName = args.projectName ?? await inferProjectName(targetRoot);
|
|
49
|
+
const installer = new Installer({
|
|
50
|
+
targetRoot,
|
|
51
|
+
projectName,
|
|
52
|
+
release: args.release,
|
|
53
|
+
force: args.force,
|
|
54
|
+
dryRun: args.dryRun
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await installer.init({ includeDocumentation: args.documentation });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseArgs(argv) {
|
|
61
|
+
const options = {
|
|
62
|
+
command: 'help',
|
|
63
|
+
target: process.cwd(),
|
|
64
|
+
projectName: undefined,
|
|
65
|
+
release: defaultRelease,
|
|
66
|
+
force: false,
|
|
67
|
+
dryRun: false,
|
|
68
|
+
documentation: true,
|
|
69
|
+
help: false,
|
|
70
|
+
version: false
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const positionals = [];
|
|
74
|
+
|
|
75
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
76
|
+
const arg = argv[index];
|
|
77
|
+
|
|
78
|
+
if (arg === '--help' || arg === '-h') {
|
|
79
|
+
options.help = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (arg === '--version' || arg === '-v') {
|
|
84
|
+
options.version = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (arg === '--force') {
|
|
89
|
+
options.force = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (arg === '--dry-run') {
|
|
94
|
+
options.dryRun = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (arg === '--no-documentation') {
|
|
99
|
+
options.documentation = false;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (arg.startsWith('--target=')) {
|
|
104
|
+
options.target = arg.slice('--target='.length);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (arg === '--target') {
|
|
109
|
+
options.target = readOptionValue(argv, index, '--target');
|
|
110
|
+
index += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (arg.startsWith('--project-name=')) {
|
|
115
|
+
options.projectName = arg.slice('--project-name='.length);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (arg === '--project-name') {
|
|
120
|
+
options.projectName = readOptionValue(argv, index, '--project-name');
|
|
121
|
+
index += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (arg.startsWith('--release=')) {
|
|
126
|
+
options.release = arg.slice('--release='.length);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (arg === '--release') {
|
|
131
|
+
options.release = readOptionValue(argv, index, '--release');
|
|
132
|
+
index += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (arg.startsWith('-')) {
|
|
137
|
+
throw new Error(`Unknown option "${arg}". Run "code-anchored-context --help".`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
positionals.push(arg);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (positionals.length > 1) {
|
|
144
|
+
throw new Error(`Unexpected arguments: ${positionals.slice(1).join(' ')}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
options.command = positionals[0] ?? (options.help || options.version ? 'meta' : 'help');
|
|
148
|
+
options.help = options.help || options.command === 'help';
|
|
149
|
+
|
|
150
|
+
return options;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readOptionValue(argv, index, optionName) {
|
|
154
|
+
const value = argv[index + 1];
|
|
155
|
+
|
|
156
|
+
if (!value || value.startsWith('-')) {
|
|
157
|
+
throw new Error(`${optionName} requires a value.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function printHelp() {
|
|
164
|
+
console.log(`code-anchored-context
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
code-anchored-context init [options]
|
|
168
|
+
|
|
169
|
+
Options:
|
|
170
|
+
--target <path> Project root to install into. Defaults to cwd.
|
|
171
|
+
--project-name <name> Name used to replace PROJECT_NAME placeholders.
|
|
172
|
+
--release <slug> Initial release slug. Defaults to ${defaultRelease}.
|
|
173
|
+
--no-documentation Skip the Documentation/ starter files.
|
|
174
|
+
--force Replace existing generated directories.
|
|
175
|
+
--dry-run Show planned changes without writing files.
|
|
176
|
+
-h, --help Show help.
|
|
177
|
+
-v, --version Show package version.
|
|
178
|
+
|
|
179
|
+
Examples:
|
|
180
|
+
npx code-anchored-context init --project-name "My App"
|
|
181
|
+
npx code-anchored-context init --release v1_0_0 --no-documentation
|
|
182
|
+
`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function validateRelease(release) {
|
|
186
|
+
if (!/^[A-Za-z0-9._-]+$/.test(release)) {
|
|
187
|
+
throw new Error('Release must contain only letters, numbers, dots, underscores, or hyphens.');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function inferProjectName(targetRoot) {
|
|
192
|
+
try {
|
|
193
|
+
const packageJson = JSON.parse(
|
|
194
|
+
await readFile(path.join(targetRoot, 'package.json'), 'utf8')
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (typeof packageJson.name === 'string' && packageJson.name.trim()) {
|
|
198
|
+
return packageJson.name.replace(/^@[^/]+\//, '');
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Fall through to the directory name.
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return path.basename(targetRoot);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class Installer {
|
|
208
|
+
constructor({ targetRoot, projectName, release, force, dryRun }) {
|
|
209
|
+
this.targetRoot = targetRoot;
|
|
210
|
+
this.projectName = projectName;
|
|
211
|
+
this.release = release;
|
|
212
|
+
this.force = force;
|
|
213
|
+
this.dryRun = dryRun;
|
|
214
|
+
this.actions = [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async init({ includeDocumentation }) {
|
|
218
|
+
await this.ensureDirectory(this.targetRoot);
|
|
219
|
+
|
|
220
|
+
await this.installAgentsFile();
|
|
221
|
+
await this.installSkill();
|
|
222
|
+
await this.copyTemplatePath('Development', 'Development');
|
|
223
|
+
await this.renameReleasePaths();
|
|
224
|
+
|
|
225
|
+
if (includeDocumentation) {
|
|
226
|
+
await this.copyTemplatePath('Documentation', 'Documentation');
|
|
227
|
+
} else {
|
|
228
|
+
this.note('skip Documentation/ (--no-documentation)');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.printSummary();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async installAgentsFile() {
|
|
235
|
+
const targetFile = path.join(this.targetRoot, 'AGENTS.md');
|
|
236
|
+
|
|
237
|
+
if (!await exists(targetFile)) {
|
|
238
|
+
await this.copyTemplatePath('AGENTS.md', 'AGENTS.md');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const current = await readFile(targetFile, 'utf8');
|
|
243
|
+
const section = this.renderAgentSection();
|
|
244
|
+
|
|
245
|
+
if (current.includes(agentSectionStart) && current.includes(agentSectionEnd)) {
|
|
246
|
+
const updated = current.replace(
|
|
247
|
+
new RegExp(`${escapeRegExp(agentSectionStart)}[\\s\\S]*?${escapeRegExp(agentSectionEnd)}`),
|
|
248
|
+
section
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (updated === current) {
|
|
252
|
+
this.note('AGENTS.md already has Code-Anchored Context guidance');
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await this.writeFile(targetFile, updated, 'update AGENTS.md Code-Anchored Context section');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (current.includes('.agents/skills/development-initiative-context/SKILL.md')) {
|
|
261
|
+
this.note('AGENTS.md already points to the development initiative skill');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const separator = current.endsWith('\n') ? '\n' : '\n\n';
|
|
266
|
+
await this.writeFile(
|
|
267
|
+
targetFile,
|
|
268
|
+
`${current}${separator}${section}\n`,
|
|
269
|
+
'append Code-Anchored Context guidance to AGENTS.md'
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
renderAgentSection() {
|
|
274
|
+
return `${agentSectionStart}
|
|
275
|
+
## Code-Anchored Context
|
|
276
|
+
|
|
277
|
+
In-progress specs, plans, ADRs, backlog, implementation context, and
|
|
278
|
+
release-documentation notes live under [\`Development/\`](Development/).
|
|
279
|
+
Start with [\`Development/current.md\`](Development/current.md).
|
|
280
|
+
|
|
281
|
+
For behavior-changing work, use the repo-wide skill at
|
|
282
|
+
[\`.agents/skills/development-initiative-context/SKILL.md\`](.agents/skills/development-initiative-context/SKILL.md).
|
|
283
|
+
Keep initiative knowledge centralized under \`Development/\`; area
|
|
284
|
+
\`AGENTS.md\` files should point there rather than copying active plans.
|
|
285
|
+
${agentSectionEnd}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async installSkill() {
|
|
289
|
+
await this.copyTemplatePath(
|
|
290
|
+
`.agents/skills/${skillName}`,
|
|
291
|
+
`.agents/skills/${skillName}`
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const readmePath = path.join(this.targetRoot, '.agents/skills/README.md');
|
|
295
|
+
|
|
296
|
+
if (!await exists(readmePath)) {
|
|
297
|
+
await this.copyTemplatePath('.agents/skills/README.md', '.agents/skills/README.md');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const current = await readFile(readmePath, 'utf8');
|
|
302
|
+
|
|
303
|
+
if (current.includes(skillName)) {
|
|
304
|
+
this.note('.agents/skills/README.md already lists the development initiative skill');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const entry = [
|
|
309
|
+
'',
|
|
310
|
+
'## Code-Anchored Context',
|
|
311
|
+
'',
|
|
312
|
+
`- \`${skillName}\` - use central \`Development/\` initiatives for planning, implementation context, programs, planned initiatives, ADRs, backlog, and release-documentation notes.`,
|
|
313
|
+
''
|
|
314
|
+
].join('\n');
|
|
315
|
+
|
|
316
|
+
await this.writeFile(
|
|
317
|
+
readmePath,
|
|
318
|
+
`${current.trimEnd()}\n${entry}`,
|
|
319
|
+
'append development initiative skill to .agents/skills/README.md'
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async copyTemplatePath(sourceRelative, targetRelative) {
|
|
324
|
+
const sourcePath = path.join(packageRoot, sourceRelative);
|
|
325
|
+
const targetPath = path.join(this.targetRoot, targetRelative);
|
|
326
|
+
|
|
327
|
+
if (await exists(targetPath)) {
|
|
328
|
+
if (!this.force) {
|
|
329
|
+
this.note(`skip ${targetRelative} (already exists; use --force to replace)`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await this.removePath(targetPath, `replace ${targetRelative}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await this.copyRecursive(sourcePath, targetPath, targetRelative);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async copyRecursive(sourcePath, targetPath, displayPath) {
|
|
340
|
+
if (this.shouldSkipTemplatePath(displayPath)) {
|
|
341
|
+
this.note(`skip ${displayPath} (template repository context)`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const sourceStats = await stat(sourcePath);
|
|
346
|
+
|
|
347
|
+
if (sourceStats.isDirectory()) {
|
|
348
|
+
await this.ensureDirectory(targetPath, `create ${displayPath}/`);
|
|
349
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
350
|
+
|
|
351
|
+
for (const entry of entries) {
|
|
352
|
+
if (entry.name === '.DS_Store') {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
await this.copyRecursive(
|
|
357
|
+
path.join(sourcePath, entry.name),
|
|
358
|
+
path.join(targetPath, entry.name),
|
|
359
|
+
path.posix.join(displayPath, entry.name)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const contents = await readFile(sourcePath);
|
|
367
|
+
const transformed = this.transformText(contents, displayPath);
|
|
368
|
+
await this.writeFile(targetPath, transformed, `create ${displayPath}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
shouldSkipTemplatePath(displayPath) {
|
|
372
|
+
const initiativePrefix = `Development/releases/${defaultRelease}/initiatives/`;
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
displayPath.startsWith(initiativePrefix) &&
|
|
376
|
+
displayPath !== `${initiativePrefix}.gitkeep`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
transformText(contents, displayPath) {
|
|
381
|
+
if (displayPath === 'Development/current.md') {
|
|
382
|
+
return this.renderStarterCurrent();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (displayPath === `Development/releases/${defaultRelease}/backlog.md`) {
|
|
386
|
+
return this.renderStarterReleaseBacklog();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let text = contents.toString('utf8');
|
|
390
|
+
text = text.replaceAll('PROJECT_NAME', this.projectName);
|
|
391
|
+
text = text.replaceAll(defaultRelease, this.release);
|
|
392
|
+
text = text.replaceAll(defaultReleaseLabel, this.releaseLabel());
|
|
393
|
+
return text;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
renderStarterCurrent() {
|
|
397
|
+
return `# Current Development Context
|
|
398
|
+
|
|
399
|
+
Current release: \`${this.release}\`
|
|
400
|
+
|
|
401
|
+
Start here:
|
|
402
|
+
|
|
403
|
+
- \`releases/${this.release}/README.md\`
|
|
404
|
+
- \`releases/${this.release}/backlog.md\`
|
|
405
|
+
- \`releases/${this.release}/initiatives/\`
|
|
406
|
+
|
|
407
|
+
Durable or deferred context:
|
|
408
|
+
|
|
409
|
+
- \`programs/\`
|
|
410
|
+
- \`backlog/\`
|
|
411
|
+
|
|
412
|
+
Changing the current release is a release transition. Use:
|
|
413
|
+
|
|
414
|
+
- \`_templates/release-transition.md\`
|
|
415
|
+
|
|
416
|
+
Active programs:
|
|
417
|
+
|
|
418
|
+
- None yet.
|
|
419
|
+
|
|
420
|
+
Registered initiatives:
|
|
421
|
+
|
|
422
|
+
- None yet.
|
|
423
|
+
|
|
424
|
+
To start an initiative, copy \`Development/_templates/initiative/\` into
|
|
425
|
+
\`Development/releases/${this.release}/initiatives/<initiative-slug>/\`, then update the
|
|
426
|
+
copied \`README.md\` first so agents have a clear entry point before editing the
|
|
427
|
+
supporting files.
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
renderStarterReleaseBacklog() {
|
|
432
|
+
return `# ${this.releaseLabel()} Backlog
|
|
433
|
+
|
|
434
|
+
This file tracks release-level development context that is not yet captured
|
|
435
|
+
by an initiative, plus a short summary of initiative progress once
|
|
436
|
+
initiatives exist.
|
|
437
|
+
|
|
438
|
+
## Loose Candidates
|
|
439
|
+
|
|
440
|
+
No backlog items recorded yet.
|
|
441
|
+
|
|
442
|
+
## Initiative Summary
|
|
443
|
+
|
|
444
|
+
No initiatives registered yet.
|
|
445
|
+
`;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
releaseLabel() {
|
|
449
|
+
return this.release.replaceAll('_', '.');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async renameReleasePaths() {
|
|
453
|
+
if (this.release === defaultRelease) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
await this.renamePath(
|
|
458
|
+
path.join(this.targetRoot, 'Development/releases', defaultRelease),
|
|
459
|
+
path.join(this.targetRoot, 'Development/releases', this.release),
|
|
460
|
+
`rename Development/releases/${defaultRelease} to Development/releases/${this.release}`
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
await this.renamePath(
|
|
464
|
+
path.join(this.targetRoot, 'Development/_templates/program/releases', `${defaultRelease}.md`),
|
|
465
|
+
path.join(this.targetRoot, 'Development/_templates/program/releases', `${this.release}.md`),
|
|
466
|
+
`rename Development/_templates/program/releases/${defaultRelease}.md to ${this.release}.md`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async renamePath(from, to, message) {
|
|
471
|
+
if (this.dryRun) {
|
|
472
|
+
this.note(message);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!await exists(from)) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (await exists(to)) {
|
|
481
|
+
if (!this.force) {
|
|
482
|
+
this.note(`skip ${path.relative(this.targetRoot, to)} (already exists; use --force to replace)`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
await this.removePath(to, `replace ${path.relative(this.targetRoot, to)}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.note(message);
|
|
490
|
+
|
|
491
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
492
|
+
await rename(from, to);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async ensureDirectory(directory, message) {
|
|
496
|
+
if (message) {
|
|
497
|
+
this.note(message);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (!this.dryRun) {
|
|
501
|
+
await mkdir(directory, { recursive: true });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async removePath(filePath, message) {
|
|
506
|
+
this.note(message);
|
|
507
|
+
|
|
508
|
+
if (!this.dryRun) {
|
|
509
|
+
await rm(filePath, { recursive: true, force: true });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async writeFile(filePath, contents, message) {
|
|
514
|
+
this.note(message);
|
|
515
|
+
|
|
516
|
+
if (!this.dryRun) {
|
|
517
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
518
|
+
await writeFile(filePath, contents);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
note(message) {
|
|
523
|
+
this.actions.push(message);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
printSummary() {
|
|
527
|
+
const prefix = this.dryRun ? '[dry-run] ' : '';
|
|
528
|
+
|
|
529
|
+
for (const action of this.actions) {
|
|
530
|
+
console.log(`${prefix}${action}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
console.log(`${prefix}Code-Anchored Context ready for ${this.projectName}.`);
|
|
534
|
+
|
|
535
|
+
if (!this.dryRun) {
|
|
536
|
+
console.log(`Next: ask your agent to read ${path.join(this.targetRoot, 'AGENTS.md')}.`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function exists(filePath) {
|
|
542
|
+
try {
|
|
543
|
+
await access(filePath, fsConstants.F_OK);
|
|
544
|
+
return true;
|
|
545
|
+
} catch {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function escapeRegExp(value) {
|
|
551
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
main().catch((error) => {
|
|
555
|
+
console.error(error.message);
|
|
556
|
+
console.error(`Run "${path.basename(execPath)} ${path.relative(process.cwd(), fileURLToPath(import.meta.url))} --help" for usage.`);
|
|
557
|
+
process.exitCode = 1;
|
|
558
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "code-anchored-context",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install repo-local agent context, development initiatives, and release-anchored documentation scaffolding into an existing project.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"code-anchored-context": "./bin/code-anchored-context.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"AGENTS.md",
|
|
12
|
+
".agents/",
|
|
13
|
+
"Development/AGENTS.md",
|
|
14
|
+
"Development/README.md",
|
|
15
|
+
"Development/_templates/",
|
|
16
|
+
"Development/backlog/",
|
|
17
|
+
"Development/code-anchored-context-structure.md",
|
|
18
|
+
"Development/code-anchored-context-why.md",
|
|
19
|
+
"Development/code-anchored-context.html",
|
|
20
|
+
"Development/current.md",
|
|
21
|
+
"Development/giving-ai-agents-context-around-code.md",
|
|
22
|
+
"Development/programs/",
|
|
23
|
+
"Development/releases/v0_1_0/README.md",
|
|
24
|
+
"Development/releases/v0_1_0/backlog.md",
|
|
25
|
+
"Development/releases/v0_1_0/initiatives/.gitkeep",
|
|
26
|
+
"Development/terminology.md",
|
|
27
|
+
"Documentation/",
|
|
28
|
+
"bin/",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "node --test"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"agents",
|
|
40
|
+
"codex",
|
|
41
|
+
"ai",
|
|
42
|
+
"documentation",
|
|
43
|
+
"development-context"
|
|
44
|
+
]
|
|
45
|
+
}
|