repo-context-center 0.1.2
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 +21 -0
- package/README.md +164 -0
- package/dist/cli/commands/archive.d.ts +2 -0
- package/dist/cli/commands/archive.js +65 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +51 -0
- package/dist/cli/commands/validate.d.ts +2 -0
- package/dist/cli/commands/validate.js +60 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +61 -0
- package/dist/core/archiver.d.ts +16 -0
- package/dist/core/archiver.js +146 -0
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.js +35 -0
- package/dist/core/contextFiles.d.ts +4 -0
- package/dist/core/contextFiles.js +10 -0
- package/dist/core/fileSystem.d.ts +5 -0
- package/dist/core/fileSystem.js +35 -0
- package/dist/core/templateInstaller.d.ts +12 -0
- package/dist/core/templateInstaller.js +31 -0
- package/dist/core/validator.d.ts +9 -0
- package/dist/core/validator.js +60 -0
- package/dist/templates/generic/AGENTS.md +14 -0
- package/dist/templates/generic/docs/ai-context/CHANGE_LOG.md +15 -0
- package/dist/templates/generic/docs/ai-context/COMMUNICATION_MODE.md +14 -0
- package/dist/templates/generic/docs/ai-context/DEPENDENCY_MAP.md +16 -0
- package/dist/templates/generic/docs/ai-context/DO_NOT_READ.md +17 -0
- package/dist/templates/generic/docs/ai-context/HOTSPOTS.md +18 -0
- package/dist/templates/generic/docs/ai-context/LESSONS_LEARNED.md +19 -0
- package/dist/templates/generic/docs/ai-context/MODULE_INDEX.md +16 -0
- package/dist/templates/generic/docs/ai-context/PROJECT_MAP.md +17 -0
- package/dist/templates/generic/docs/ai-context/RISK_REGISTER.md +18 -0
- package/dist/templates/generic/docs/ai-context/SYMBOL_MAP.md +17 -0
- package/dist/templates/generic/docs/ai-context/TASK_ROUTING.md +18 -0
- package/dist/templates/generic/docs/ai-context/TOKEN_BUDGET.md +16 -0
- package/dist/templates/generic/index.d.ts +9 -0
- package/dist/templates/generic/index.js +36 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gökhan Yigit
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# repo-context-center
|
|
2
|
+
|
|
3
|
+
A context layer for AI coding agents.
|
|
4
|
+
|
|
5
|
+
`repo-context-center` installs a small set of repository instructions and context maps into a target repo so AI coding tools can find the right context faster and avoid rereading noisy files.
|
|
6
|
+
|
|
7
|
+
It is not an AI agent. It is not a code analyzer. It does not understand your project automatically yet. It gives agents a durable place to store routing notes, module maps, risk notes, token guidance, and lessons learned.
|
|
8
|
+
|
|
9
|
+
It is designed to work with Codex, Claude Code, Cursor, Copilot-style agents, and other tools that read repository instructions.
|
|
10
|
+
|
|
11
|
+
## What Problem This Solves
|
|
12
|
+
|
|
13
|
+
AI coding agents often waste context on repeated discovery:
|
|
14
|
+
|
|
15
|
+
- Which files matter for this task?
|
|
16
|
+
- Which generated folders should be skipped?
|
|
17
|
+
- Where are the risky boundaries?
|
|
18
|
+
- What did the last session already learn?
|
|
19
|
+
- Which docs should be updated after a change?
|
|
20
|
+
|
|
21
|
+
This project adds a generic Repository Context Center to answer those questions inside the repo.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
From a clone of this project:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install
|
|
29
|
+
npm run build
|
|
30
|
+
npm link
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then, inside a target repository:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
repo-context-center init
|
|
37
|
+
repo-context-center validate
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Preview installation without writing files:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
repo-context-center init --dry-run
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Overwrite existing context templates:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
repo-context-center init --force
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installed Files
|
|
53
|
+
|
|
54
|
+
`repo-context-center init` installs generic templates:
|
|
55
|
+
|
|
56
|
+
- `AGENTS.md`
|
|
57
|
+
- `docs/ai-context/COMMUNICATION_MODE.md`
|
|
58
|
+
- `docs/ai-context/TASK_ROUTING.md`
|
|
59
|
+
- `docs/ai-context/MODULE_INDEX.md`
|
|
60
|
+
- `docs/ai-context/PROJECT_MAP.md`
|
|
61
|
+
- `docs/ai-context/RISK_REGISTER.md`
|
|
62
|
+
- `docs/ai-context/DEPENDENCY_MAP.md`
|
|
63
|
+
- `docs/ai-context/SYMBOL_MAP.md`
|
|
64
|
+
- `docs/ai-context/TOKEN_BUDGET.md`
|
|
65
|
+
- `docs/ai-context/DO_NOT_READ.md`
|
|
66
|
+
- `docs/ai-context/HOTSPOTS.md`
|
|
67
|
+
- `docs/ai-context/LESSONS_LEARNED.md`
|
|
68
|
+
- `docs/ai-context/CHANGE_LOG.md`
|
|
69
|
+
- `docs/ai-context/archive/`
|
|
70
|
+
|
|
71
|
+
It also creates `.repo-context-center/config.json`.
|
|
72
|
+
|
|
73
|
+
## Commands
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
repo-context-center --help
|
|
77
|
+
repo-context-center init [--dry-run] [--force]
|
|
78
|
+
repo-context-center validate [--strict]
|
|
79
|
+
repo-context-center archive [--keep <number>] [--dry-run]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `init`: install the generic context templates.
|
|
83
|
+
- `validate`: check that required context files exist and report warnings.
|
|
84
|
+
Missing `.repo-context-center/config.json` is a warning by default and a
|
|
85
|
+
failure with `--strict`.
|
|
86
|
+
- `archive`: archive older entries from long-running context files; defaults to keeping 50 entries.
|
|
87
|
+
|
|
88
|
+
## Recommended AI Agent Workflow
|
|
89
|
+
|
|
90
|
+
Ask the agent to:
|
|
91
|
+
|
|
92
|
+
1. Read `AGENTS.md`.
|
|
93
|
+
2. Follow `docs/ai-context/TASK_ROUTING.md`.
|
|
94
|
+
3. Read only the context files relevant to the task.
|
|
95
|
+
4. Check `DO_NOT_READ.md` before broad search.
|
|
96
|
+
5. Update `LESSONS_LEARNED.md` and `CHANGE_LOG.md` when durable context changes.
|
|
97
|
+
|
|
98
|
+
See [docs/agent-usage.md](docs/agent-usage.md).
|
|
99
|
+
|
|
100
|
+
## Token-Saving Strategy
|
|
101
|
+
|
|
102
|
+
The context center is meant to reduce repeated discovery, not replace source reading. It works by keeping small, stable maps near the repo:
|
|
103
|
+
|
|
104
|
+
- route tasks before opening many files;
|
|
105
|
+
- skip generated and vendored paths by default;
|
|
106
|
+
- keep module, dependency, risk, and symbol notes compact;
|
|
107
|
+
- archive long-running notes before they become noisy.
|
|
108
|
+
|
|
109
|
+
See [docs/token-strategy.md](docs/token-strategy.md).
|
|
110
|
+
|
|
111
|
+
## Example: Before/After Session Behavior
|
|
112
|
+
|
|
113
|
+
Before:
|
|
114
|
+
|
|
115
|
+
> The agent scans broadly, opens generated files, rediscovers entrypoints, misses a risky shared module, and repeats the same investigation next session.
|
|
116
|
+
|
|
117
|
+
After:
|
|
118
|
+
|
|
119
|
+
> The agent reads `AGENTS.md`, follows task routing, opens the relevant module map, checks hotspots, edits a smaller set of files, runs focused tests, and records durable lessons.
|
|
120
|
+
|
|
121
|
+
More examples are in [docs/examples.md](docs/examples.md).
|
|
122
|
+
|
|
123
|
+
## Further Reading
|
|
124
|
+
|
|
125
|
+
- [Concept](docs/concept.md)
|
|
126
|
+
- [Agent usage](docs/agent-usage.md)
|
|
127
|
+
- [Token strategy](docs/token-strategy.md)
|
|
128
|
+
- [Examples](docs/examples.md)
|
|
129
|
+
|
|
130
|
+
## Supported Project Types
|
|
131
|
+
|
|
132
|
+
The generic templates are language-agnostic. They can be installed in JavaScript, TypeScript, Python, Go, Rust, Ruby, Java, monorepos, docs repos, and mixed stacks.
|
|
133
|
+
|
|
134
|
+
Project-specific templates and repo scanning are not implemented yet.
|
|
135
|
+
|
|
136
|
+
## Roadmap
|
|
137
|
+
|
|
138
|
+
- Generic template installation and validation.
|
|
139
|
+
- Context file archiving.
|
|
140
|
+
- Project-specific template packs.
|
|
141
|
+
- Optional repository scanning to prefill maps.
|
|
142
|
+
- Safer update workflows for existing context centers.
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
npm install
|
|
148
|
+
npm run build
|
|
149
|
+
npm test
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Manual Release
|
|
153
|
+
|
|
154
|
+
This package is prepared for manual npm publishing.
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
npm run build
|
|
158
|
+
npm test
|
|
159
|
+
npm pack
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The package publishes the compiled `dist/` output, including the generic context
|
|
163
|
+
templates copied during build. `prepublishOnly` runs build and tests before a
|
|
164
|
+
manual `npm publish`.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.archiveCommand = archiveCommand;
|
|
4
|
+
const archiver_1 = require("../../core/archiver");
|
|
5
|
+
function parseArchiveOptions(args) {
|
|
6
|
+
const options = {
|
|
7
|
+
keep: 50,
|
|
8
|
+
dryRun: false
|
|
9
|
+
};
|
|
10
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
11
|
+
const arg = args[index];
|
|
12
|
+
if (arg === "--dry-run") {
|
|
13
|
+
options.dryRun = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (arg === "--keep") {
|
|
17
|
+
const value = args[index + 1];
|
|
18
|
+
if (!value) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
options.keep = Number(value);
|
|
22
|
+
index += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
if (!Number.isInteger(options.keep) || options.keep < 1) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return options;
|
|
31
|
+
}
|
|
32
|
+
function formatResult(result, dryRun) {
|
|
33
|
+
const lines = [dryRun ? "Archive dry run" : "Archive complete", ""];
|
|
34
|
+
for (const file of result.files) {
|
|
35
|
+
if (file.missing) {
|
|
36
|
+
lines.push(`Skipped missing optional file: ${file.sourcePath}`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (file.archived === 0) {
|
|
40
|
+
lines.push(`No archive needed: ${file.sourcePath} (${file.kept} entries)`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const action = dryRun ? "Would archive" : "Archived";
|
|
44
|
+
lines.push(`${action} ${file.archived} entries from ${file.sourcePath} to ${file.archivePath}`);
|
|
45
|
+
lines.push(`Kept ${file.kept} entries in ${file.sourcePath}`);
|
|
46
|
+
}
|
|
47
|
+
if (dryRun) {
|
|
48
|
+
lines.push("", "No files were written.");
|
|
49
|
+
}
|
|
50
|
+
return `${lines.join("\n")}\n`;
|
|
51
|
+
}
|
|
52
|
+
async function archiveCommand(io, args = []) {
|
|
53
|
+
const options = parseArchiveOptions(args);
|
|
54
|
+
if (!options) {
|
|
55
|
+
io.stderr("Unknown archive option. Supported options: --keep <number>, --dry-run\n");
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
const result = await (0, archiver_1.archiveContextFiles)({
|
|
59
|
+
cwd: io.cwd,
|
|
60
|
+
keep: options.keep,
|
|
61
|
+
dryRun: options.dryRun
|
|
62
|
+
});
|
|
63
|
+
io.stdout(formatResult(result, options.dryRun));
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initCommand = initCommand;
|
|
4
|
+
const config_1 = require("../../core/config");
|
|
5
|
+
const templateInstaller_1 = require("../../core/templateInstaller");
|
|
6
|
+
function parseInitOptions(args) {
|
|
7
|
+
return {
|
|
8
|
+
force: args.includes("--force"),
|
|
9
|
+
dryRun: args.includes("--dry-run")
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function formatInstallMessage(result, dryRun) {
|
|
13
|
+
if (dryRun) {
|
|
14
|
+
if (result.action === "skip") {
|
|
15
|
+
return `Would skip ${result.type}: ${result.path} already exists\n`;
|
|
16
|
+
}
|
|
17
|
+
return `Would ${result.action} ${result.type}: ${result.path}\n`;
|
|
18
|
+
}
|
|
19
|
+
if (result.action === "skip") {
|
|
20
|
+
return `Skipped ${result.type}: ${result.path} already exists\n`;
|
|
21
|
+
}
|
|
22
|
+
const verb = result.action === "overwrite" ? "Overwrote" : "Created";
|
|
23
|
+
return `${verb} ${result.type}: ${result.path}\n`;
|
|
24
|
+
}
|
|
25
|
+
async function initCommand(io, args = []) {
|
|
26
|
+
const options = parseInitOptions(args);
|
|
27
|
+
const unknownFlag = args.find((arg) => arg.startsWith("--") && arg !== "--force" && arg !== "--dry-run");
|
|
28
|
+
if (unknownFlag) {
|
|
29
|
+
io.stderr(`Unknown init option: ${unknownFlag}\n`);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const results = await (0, templateInstaller_1.installGenericTemplates)({
|
|
33
|
+
cwd: io.cwd,
|
|
34
|
+
force: options.force,
|
|
35
|
+
dryRun: options.dryRun
|
|
36
|
+
});
|
|
37
|
+
for (const result of results) {
|
|
38
|
+
io.stdout(formatInstallMessage(result, options.dryRun));
|
|
39
|
+
}
|
|
40
|
+
if (options.dryRun) {
|
|
41
|
+
io.stdout("Dry run complete. No files were written.\n");
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
if (await (0, config_1.hasConfig)(io.cwd)) {
|
|
45
|
+
io.stdout(`Config already exists: ${(0, config_1.getConfigPath)(io.cwd)}\n`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const configPath = await (0, config_1.writeDefaultConfig)(io.cwd);
|
|
49
|
+
io.stdout(`Initialized repo-context-center config: ${configPath}\n`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateCommand = validateCommand;
|
|
4
|
+
const validator_1 = require("../../core/validator");
|
|
5
|
+
function parseValidateOptions(args) {
|
|
6
|
+
const unknownFlag = args.find((arg) => arg.startsWith("--") && arg !== "--strict");
|
|
7
|
+
if (unknownFlag) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
strict: args.includes("--strict")
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function formatReport(report, strict) {
|
|
15
|
+
const lines = ["repo-context-center validation report", ""];
|
|
16
|
+
if (report.missing.length === 0) {
|
|
17
|
+
lines.push("Required files: ok");
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push(`Missing required files: ${report.missing.length}`);
|
|
21
|
+
for (const issue of report.missing) {
|
|
22
|
+
lines.push(` - ${issue.path}: ${issue.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (report.warnings.length === 0) {
|
|
26
|
+
lines.push("Warnings: none");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lines.push(`Warnings: ${report.warnings.length}`);
|
|
30
|
+
for (const issue of report.warnings) {
|
|
31
|
+
lines.push(` - ${issue.path}: ${issue.message}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (report.missing.length > 0) {
|
|
35
|
+
lines.push("", "Result: failed");
|
|
36
|
+
}
|
|
37
|
+
else if (strict && report.warnings.length > 0) {
|
|
38
|
+
lines.push("", "Result: failed in strict mode");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines.push("", "Result: passed");
|
|
42
|
+
}
|
|
43
|
+
return `${lines.join("\n")}\n`;
|
|
44
|
+
}
|
|
45
|
+
async function validateCommand(io, args = []) {
|
|
46
|
+
const options = parseValidateOptions(args);
|
|
47
|
+
if (!options) {
|
|
48
|
+
io.stderr("Unknown validate option. Supported options: --strict\n");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const report = await (0, validator_1.validateContextSetup)(io.cwd);
|
|
52
|
+
const hasFailures = report.missing.length > 0 || (options.strict && report.warnings.length > 0);
|
|
53
|
+
const output = formatReport(report, options.strict);
|
|
54
|
+
if (hasFailures) {
|
|
55
|
+
io.stderr(output);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
io.stdout(output);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getHelpText = getHelpText;
|
|
5
|
+
exports.run = run;
|
|
6
|
+
const archive_1 = require("./commands/archive");
|
|
7
|
+
const init_1 = require("./commands/init");
|
|
8
|
+
const validate_1 = require("./commands/validate");
|
|
9
|
+
const commands = {
|
|
10
|
+
init: init_1.initCommand,
|
|
11
|
+
validate: validate_1.validateCommand,
|
|
12
|
+
archive: archive_1.archiveCommand
|
|
13
|
+
};
|
|
14
|
+
const helpText = `repo-context-center
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
repo-context-center <command>
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
init Install generic context templates and config
|
|
21
|
+
Options: --dry-run, --force
|
|
22
|
+
validate Validate required context files and warnings
|
|
23
|
+
Options: --strict
|
|
24
|
+
archive Archive older CHANGE_LOG and LESSONS_LEARNED entries
|
|
25
|
+
Options: --keep <number>, --dry-run
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
-h, --help Show this help
|
|
29
|
+
`;
|
|
30
|
+
function defaultIO() {
|
|
31
|
+
return {
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
stdout: (message) => process.stdout.write(message),
|
|
34
|
+
stderr: (message) => process.stderr.write(message)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getHelpText() {
|
|
38
|
+
return helpText;
|
|
39
|
+
}
|
|
40
|
+
async function run(argv = process.argv.slice(2), io = defaultIO()) {
|
|
41
|
+
const [command, ...args] = argv;
|
|
42
|
+
if (!command || command === "--help" || command === "-h") {
|
|
43
|
+
io.stdout(helpText);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
const handler = commands[command];
|
|
47
|
+
if (!handler) {
|
|
48
|
+
io.stderr(`Unknown command: ${command}\n\n${helpText}`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
return handler(io, args);
|
|
52
|
+
}
|
|
53
|
+
if (require.main === module) {
|
|
54
|
+
run().then((exitCode) => {
|
|
55
|
+
process.exitCode = exitCode;
|
|
56
|
+
}).catch((error) => {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
process.stderr.write(`${message}\n`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ArchiveOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
keep: number;
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ArchiveFileResult {
|
|
7
|
+
sourcePath: string;
|
|
8
|
+
archivePath: string;
|
|
9
|
+
kept: number;
|
|
10
|
+
archived: number;
|
|
11
|
+
missing: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ArchiveResult {
|
|
14
|
+
files: ArchiveFileResult[];
|
|
15
|
+
}
|
|
16
|
+
export declare function archiveContextFiles(options: ArchiveOptions): Promise<ArchiveResult>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.archiveContextFiles = archiveContextFiles;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const fileSystem_1 = require("./fileSystem");
|
|
10
|
+
const targetFiles = [
|
|
11
|
+
{
|
|
12
|
+
sourcePath: "docs/ai-context/CHANGE_LOG.md",
|
|
13
|
+
archivePath: "docs/ai-context/archive/CHANGE_LOG_ARCHIVE.md",
|
|
14
|
+
archiveTitle: "Change Log Archive"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
sourcePath: "docs/ai-context/LESSONS_LEARNED.md",
|
|
18
|
+
archivePath: "docs/ai-context/archive/LESSONS_LEARNED_ARCHIVE.md",
|
|
19
|
+
archiveTitle: "Lessons Learned Archive"
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
function isTableRow(line) {
|
|
23
|
+
return line.trim().startsWith("|") && line.trim().endsWith("|");
|
|
24
|
+
}
|
|
25
|
+
function isDividerRow(line) {
|
|
26
|
+
return /^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|$/.test(line.trim());
|
|
27
|
+
}
|
|
28
|
+
function splitLines(content) {
|
|
29
|
+
return content.replace(/\r\n/g, "\n").split("\n");
|
|
30
|
+
}
|
|
31
|
+
function parseFirstTable(content) {
|
|
32
|
+
const lines = splitLines(content);
|
|
33
|
+
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
34
|
+
if (!isTableRow(lines[index]) || !isDividerRow(lines[index + 1])) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const rows = [];
|
|
38
|
+
let cursor = index + 2;
|
|
39
|
+
while (cursor < lines.length && isTableRow(lines[cursor])) {
|
|
40
|
+
rows.push(lines[cursor]);
|
|
41
|
+
cursor += 1;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
before: lines.slice(0, index),
|
|
45
|
+
header: lines[index],
|
|
46
|
+
divider: lines[index + 1],
|
|
47
|
+
rows,
|
|
48
|
+
after: lines.slice(cursor)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function firstCell(row) {
|
|
54
|
+
return row.split("|")[1]?.trim() ?? "";
|
|
55
|
+
}
|
|
56
|
+
function rowTime(row) {
|
|
57
|
+
const value = firstCell(row);
|
|
58
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const time = Date.parse(`${value}T00:00:00.000Z`);
|
|
62
|
+
return Number.isNaN(time) ? undefined : time;
|
|
63
|
+
}
|
|
64
|
+
function selectRows(rows, keep) {
|
|
65
|
+
const ranked = rows.map((row, index) => ({
|
|
66
|
+
row,
|
|
67
|
+
index,
|
|
68
|
+
time: rowTime(row)
|
|
69
|
+
}));
|
|
70
|
+
ranked.sort((left, right) => {
|
|
71
|
+
if (left.time !== undefined && right.time !== undefined && left.time !== right.time) {
|
|
72
|
+
return right.time - left.time;
|
|
73
|
+
}
|
|
74
|
+
if (left.time !== undefined && right.time === undefined) {
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
if (left.time === undefined && right.time !== undefined) {
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
return left.index - right.index;
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
keptRows: ranked.slice(0, keep).map((entry) => entry.row),
|
|
84
|
+
archivedRows: ranked.slice(keep).map((entry) => entry.row)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function renderTable(table, rows) {
|
|
88
|
+
const output = [...table.before, table.header, table.divider, ...rows, ...table.after];
|
|
89
|
+
return `${output.join("\n").replace(/\n+$/u, "")}\n`;
|
|
90
|
+
}
|
|
91
|
+
function renderArchive(title, table, rows, existingContent) {
|
|
92
|
+
const existing = existingContent?.trimEnd();
|
|
93
|
+
const existingTable = existingContent ? parseFirstTable(existingContent) : undefined;
|
|
94
|
+
if (existing && existingTable) {
|
|
95
|
+
return renderTable(existingTable, [...existingTable.rows, ...rows]);
|
|
96
|
+
}
|
|
97
|
+
if (existing) {
|
|
98
|
+
return `${existing}\n\n${table.header}\n${table.divider}\n${rows.join("\n")}\n`;
|
|
99
|
+
}
|
|
100
|
+
return `# ${title}\n\n${table.header}\n${table.divider}\n${rows.join("\n")}\n`;
|
|
101
|
+
}
|
|
102
|
+
async function archiveFile(target, options) {
|
|
103
|
+
const sourcePath = node_path_1.default.join(options.cwd, target.sourcePath);
|
|
104
|
+
const archivePath = node_path_1.default.join(options.cwd, target.archivePath);
|
|
105
|
+
if (!(await (0, fileSystem_1.pathExists)(sourcePath))) {
|
|
106
|
+
return {
|
|
107
|
+
sourcePath: target.sourcePath,
|
|
108
|
+
archivePath: target.archivePath,
|
|
109
|
+
kept: 0,
|
|
110
|
+
archived: 0,
|
|
111
|
+
missing: true
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const content = await (0, promises_1.readFile)(sourcePath, "utf8");
|
|
115
|
+
const table = parseFirstTable(content);
|
|
116
|
+
if (!table || table.rows.length <= options.keep) {
|
|
117
|
+
return {
|
|
118
|
+
sourcePath: target.sourcePath,
|
|
119
|
+
archivePath: target.archivePath,
|
|
120
|
+
kept: table?.rows.length ?? 0,
|
|
121
|
+
archived: 0,
|
|
122
|
+
missing: false
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const { keptRows, archivedRows } = selectRows(table.rows, options.keep);
|
|
126
|
+
if (!options.dryRun) {
|
|
127
|
+
const existingArchive = (await (0, fileSystem_1.pathExists)(archivePath)) ? await (0, promises_1.readFile)(archivePath, "utf8") : undefined;
|
|
128
|
+
await (0, fileSystem_1.writeTextFile)(sourcePath, renderTable(table, keptRows));
|
|
129
|
+
await (0, fileSystem_1.ensureDir)(node_path_1.default.dirname(archivePath));
|
|
130
|
+
await (0, fileSystem_1.writeTextFile)(archivePath, renderArchive(target.archiveTitle, table, archivedRows, existingArchive));
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
sourcePath: target.sourcePath,
|
|
134
|
+
archivePath: target.archivePath,
|
|
135
|
+
kept: keptRows.length,
|
|
136
|
+
archived: archivedRows.length,
|
|
137
|
+
missing: false
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function archiveContextFiles(options) {
|
|
141
|
+
if (!Number.isInteger(options.keep) || options.keep < 1) {
|
|
142
|
+
throw new Error("--keep must be a positive integer");
|
|
143
|
+
}
|
|
144
|
+
const files = await Promise.all(targetFiles.map((target) => archiveFile(target, options)));
|
|
145
|
+
return { files };
|
|
146
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface RepoContextConfig {
|
|
2
|
+
version: 1;
|
|
3
|
+
createdBy: "repo-context-center";
|
|
4
|
+
}
|
|
5
|
+
export declare const configDirName = ".repo-context-center";
|
|
6
|
+
export declare const configFileName = "config.json";
|
|
7
|
+
export declare function getConfigPath(cwd: string): string;
|
|
8
|
+
export declare function createDefaultConfig(): RepoContextConfig;
|
|
9
|
+
export declare function hasConfig(cwd: string): Promise<boolean>;
|
|
10
|
+
export declare function writeDefaultConfig(cwd: string): Promise<string>;
|
|
11
|
+
export declare function readConfig(cwd: string): Promise<RepoContextConfig>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configFileName = exports.configDirName = void 0;
|
|
7
|
+
exports.getConfigPath = getConfigPath;
|
|
8
|
+
exports.createDefaultConfig = createDefaultConfig;
|
|
9
|
+
exports.hasConfig = hasConfig;
|
|
10
|
+
exports.writeDefaultConfig = writeDefaultConfig;
|
|
11
|
+
exports.readConfig = readConfig;
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const fileSystem_1 = require("./fileSystem");
|
|
14
|
+
exports.configDirName = ".repo-context-center";
|
|
15
|
+
exports.configFileName = "config.json";
|
|
16
|
+
function getConfigPath(cwd) {
|
|
17
|
+
return node_path_1.default.join(cwd, exports.configDirName, exports.configFileName);
|
|
18
|
+
}
|
|
19
|
+
function createDefaultConfig() {
|
|
20
|
+
return {
|
|
21
|
+
version: 1,
|
|
22
|
+
createdBy: "repo-context-center"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function hasConfig(cwd) {
|
|
26
|
+
return (0, fileSystem_1.pathExists)(getConfigPath(cwd));
|
|
27
|
+
}
|
|
28
|
+
async function writeDefaultConfig(cwd) {
|
|
29
|
+
const configPath = getConfigPath(cwd);
|
|
30
|
+
await (0, fileSystem_1.writeJsonFile)(configPath, createDefaultConfig());
|
|
31
|
+
return configPath;
|
|
32
|
+
}
|
|
33
|
+
async function readConfig(cwd) {
|
|
34
|
+
return (0, fileSystem_1.readJsonFile)(getConfigPath(cwd));
|
|
35
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const requiredContextFiles: readonly ["AGENTS.md", "docs/ai-context/COMMUNICATION_MODE.md", "docs/ai-context/TASK_ROUTING.md", "docs/ai-context/MODULE_INDEX.md", "docs/ai-context/PROJECT_MAP.md", "docs/ai-context/RISK_REGISTER.md", "docs/ai-context/DEPENDENCY_MAP.md", "docs/ai-context/SYMBOL_MAP.md", "docs/ai-context/TOKEN_BUDGET.md", "docs/ai-context/DO_NOT_READ.md", "docs/ai-context/HOTSPOTS.md", "docs/ai-context/LESSONS_LEARNED.md", "docs/ai-context/CHANGE_LOG.md"];
|
|
2
|
+
export type RequiredContextFile = (typeof requiredContextFiles)[number];
|
|
3
|
+
export declare const contextArchiveDir = "docs/ai-context/archive";
|
|
4
|
+
export declare const contextSizeLimits: Partial<Record<RequiredContextFile, number>>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.contextSizeLimits = exports.contextArchiveDir = exports.requiredContextFiles = void 0;
|
|
4
|
+
const generic_1 = require("../templates/generic");
|
|
5
|
+
exports.requiredContextFiles = generic_1.genericTemplateFiles;
|
|
6
|
+
exports.contextArchiveDir = "docs/ai-context/archive";
|
|
7
|
+
exports.contextSizeLimits = {
|
|
8
|
+
"docs/ai-context/CHANGE_LOG.md": 32 * 1024,
|
|
9
|
+
"docs/ai-context/TASK_ROUTING.md": 16 * 1024
|
|
10
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function pathExists(filePath: string): Promise<boolean>;
|
|
2
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
3
|
+
export declare function writeJsonFile(filePath: string, value: unknown): Promise<void>;
|
|
4
|
+
export declare function writeTextFile(filePath: string, content: string): Promise<void>;
|
|
5
|
+
export declare function readJsonFile<T>(filePath: string): Promise<T>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pathExists = pathExists;
|
|
7
|
+
exports.ensureDir = ensureDir;
|
|
8
|
+
exports.writeJsonFile = writeJsonFile;
|
|
9
|
+
exports.writeTextFile = writeTextFile;
|
|
10
|
+
exports.readJsonFile = readJsonFile;
|
|
11
|
+
const promises_1 = require("node:fs/promises");
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
async function pathExists(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
await (0, promises_1.access)(filePath);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function ensureDir(dirPath) {
|
|
23
|
+
await (0, promises_1.mkdir)(dirPath, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
async function writeJsonFile(filePath, value) {
|
|
26
|
+
await ensureDir(node_path_1.default.dirname(filePath));
|
|
27
|
+
await (0, promises_1.writeFile)(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
28
|
+
}
|
|
29
|
+
async function writeTextFile(filePath, content) {
|
|
30
|
+
await ensureDir(node_path_1.default.dirname(filePath));
|
|
31
|
+
await (0, promises_1.writeFile)(filePath, content, "utf8");
|
|
32
|
+
}
|
|
33
|
+
async function readJsonFile(filePath) {
|
|
34
|
+
return JSON.parse(await (0, promises_1.readFile)(filePath, "utf8"));
|
|
35
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface TemplateInstallOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
force?: boolean;
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export type TemplateInstallAction = "create" | "overwrite" | "skip";
|
|
7
|
+
export interface TemplateInstallResult {
|
|
8
|
+
path: string;
|
|
9
|
+
action: TemplateInstallAction;
|
|
10
|
+
type: "file" | "directory";
|
|
11
|
+
}
|
|
12
|
+
export declare function installGenericTemplates(options: TemplateInstallOptions): Promise<TemplateInstallResult[]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installGenericTemplates = installGenericTemplates;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const fileSystem_1 = require("./fileSystem");
|
|
9
|
+
const generic_1 = require("../templates/generic");
|
|
10
|
+
const archiveDir = "docs/ai-context/archive";
|
|
11
|
+
async function installGenericTemplates(options) {
|
|
12
|
+
const results = [];
|
|
13
|
+
const templates = await (0, generic_1.readGenericTemplates)();
|
|
14
|
+
for (const template of templates) {
|
|
15
|
+
const targetPath = node_path_1.default.join(options.cwd, template.path);
|
|
16
|
+
const exists = await (0, fileSystem_1.pathExists)(targetPath);
|
|
17
|
+
const action = exists ? (options.force ? "overwrite" : "skip") : "create";
|
|
18
|
+
if (!options.dryRun && action !== "skip") {
|
|
19
|
+
await (0, fileSystem_1.writeTextFile)(targetPath, template.content);
|
|
20
|
+
}
|
|
21
|
+
results.push({ path: template.path, action, type: "file" });
|
|
22
|
+
}
|
|
23
|
+
const archivePath = node_path_1.default.join(options.cwd, archiveDir);
|
|
24
|
+
const archiveExists = await (0, fileSystem_1.pathExists)(archivePath);
|
|
25
|
+
const archiveAction = archiveExists ? "skip" : "create";
|
|
26
|
+
if (!options.dryRun && archiveAction === "create") {
|
|
27
|
+
await (0, fileSystem_1.ensureDir)(archivePath);
|
|
28
|
+
}
|
|
29
|
+
results.push({ path: archiveDir, action: archiveAction, type: "directory" });
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ValidationIssue {
|
|
2
|
+
path: string;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ValidationReport {
|
|
6
|
+
missing: ValidationIssue[];
|
|
7
|
+
warnings: ValidationIssue[];
|
|
8
|
+
}
|
|
9
|
+
export declare function validateContextSetup(cwd: string): Promise<ValidationReport>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateContextSetup = validateContextSetup;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const contextFiles_1 = require("./contextFiles");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const fileSystem_1 = require("./fileSystem");
|
|
12
|
+
async function getFileSize(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const fileStat = await (0, promises_1.stat)(filePath);
|
|
15
|
+
return fileStat.isFile() ? fileStat.size : undefined;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function validateRequiredFile(cwd, file) {
|
|
22
|
+
const targetPath = node_path_1.default.join(cwd, file);
|
|
23
|
+
const fileStat = await getFileSize(targetPath);
|
|
24
|
+
if (fileStat === undefined) {
|
|
25
|
+
return {
|
|
26
|
+
path: file,
|
|
27
|
+
message: "Required context file is missing"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
async function validateSize(cwd, file, maxBytes) {
|
|
33
|
+
const targetPath = node_path_1.default.join(cwd, file);
|
|
34
|
+
const fileSize = await getFileSize(targetPath);
|
|
35
|
+
if (fileSize !== undefined && fileSize > maxBytes) {
|
|
36
|
+
return {
|
|
37
|
+
path: file,
|
|
38
|
+
message: `Context file is large (${fileSize} bytes, limit ${maxBytes} bytes)`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
async function validateContextSetup(cwd) {
|
|
44
|
+
const missing = (await Promise.all(contextFiles_1.requiredContextFiles.map((file) => validateRequiredFile(cwd, file)))).filter((issue) => issue !== undefined);
|
|
45
|
+
const sizeWarnings = (await Promise.all(Object.entries(contextFiles_1.contextSizeLimits).map(([file, maxBytes]) => validateSize(cwd, file, maxBytes)))).filter((issue) => issue !== undefined);
|
|
46
|
+
const warnings = [...sizeWarnings];
|
|
47
|
+
if (!(await (0, fileSystem_1.pathExists)((0, config_1.getConfigPath)(cwd)))) {
|
|
48
|
+
warnings.push({
|
|
49
|
+
path: `${config_1.configDirName}/${config_1.configFileName}`,
|
|
50
|
+
message: "Config file is missing"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!(await (0, fileSystem_1.pathExists)(node_path_1.default.join(cwd, contextFiles_1.contextArchiveDir)))) {
|
|
54
|
+
warnings.push({
|
|
55
|
+
path: contextFiles_1.contextArchiveDir,
|
|
56
|
+
message: "Archive directory is missing"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { missing, warnings };
|
|
60
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Use this repository context center before changing code.
|
|
4
|
+
|
|
5
|
+
Start here:
|
|
6
|
+
- Read `docs/ai-context/COMMUNICATION_MODE.md`.
|
|
7
|
+
- Check `docs/ai-context/TASK_ROUTING.md` for the right path.
|
|
8
|
+
- Read only the context files relevant to the task.
|
|
9
|
+
- Update `LESSONS_LEARNED.md` and `CHANGE_LOG.md` when the work reveals durable facts.
|
|
10
|
+
|
|
11
|
+
Default rules:
|
|
12
|
+
- Prefer small, reversible changes.
|
|
13
|
+
- Do not scan generated, vendored, or ignored files unless the task requires it.
|
|
14
|
+
- Validate behavior with the narrowest useful test.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
Record context-center changes, not every code change.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Date | Change | Reason |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| YYYY-MM-DD | File or section updated | Why context changed |
|
|
10
|
+
|
|
11
|
+
Use when:
|
|
12
|
+
- A template is installed.
|
|
13
|
+
- A context map is corrected.
|
|
14
|
+
- Routing guidance changes.
|
|
15
|
+
- A stale note is removed.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Communication Mode
|
|
2
|
+
|
|
3
|
+
Purpose: align the agent's working style with the current task.
|
|
4
|
+
|
|
5
|
+
Default mode:
|
|
6
|
+
- Read first, then act.
|
|
7
|
+
- Share assumptions when context is incomplete.
|
|
8
|
+
- Ask only when a wrong assumption would be costly.
|
|
9
|
+
- Keep updates concise and tied to observable progress.
|
|
10
|
+
|
|
11
|
+
When editing:
|
|
12
|
+
- Name the files being changed.
|
|
13
|
+
- Preserve user changes.
|
|
14
|
+
- Report tests run and failures clearly.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Dependency Map
|
|
2
|
+
|
|
3
|
+
Record important internal and external dependencies.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| From | Depends On | Why It Matters |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `module` | `module/package` | Runtime, API, build, or test impact |
|
|
10
|
+
|
|
11
|
+
Use this to avoid surprise blast radius.
|
|
12
|
+
|
|
13
|
+
Update when:
|
|
14
|
+
- A shared dependency changes.
|
|
15
|
+
- A module boundary shifts.
|
|
16
|
+
- A new external package is added.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Do Not Read
|
|
2
|
+
|
|
3
|
+
Skip these unless the task explicitly requires them.
|
|
4
|
+
|
|
5
|
+
Common examples:
|
|
6
|
+
- `node_modules/`
|
|
7
|
+
- `dist/`
|
|
8
|
+
- `build/`
|
|
9
|
+
- `coverage/`
|
|
10
|
+
- `.next/`
|
|
11
|
+
- `.turbo/`
|
|
12
|
+
- Generated clients
|
|
13
|
+
- Large snapshots
|
|
14
|
+
- Vendored code
|
|
15
|
+
- Minified bundles
|
|
16
|
+
|
|
17
|
+
When an excluded file is required, explain why and read the smallest useful section.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Hotspots
|
|
2
|
+
|
|
3
|
+
Track files or flows that often cause bugs.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Hotspot | Why | Safer Move |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `path/or/flow` | Failure pattern | Test, review, or constraint |
|
|
10
|
+
|
|
11
|
+
Use for:
|
|
12
|
+
- Complex state.
|
|
13
|
+
- Concurrency.
|
|
14
|
+
- Auth or permissions.
|
|
15
|
+
- Serialization.
|
|
16
|
+
- Boundary adapters.
|
|
17
|
+
|
|
18
|
+
Remove entries when they stop being true.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Lessons Learned
|
|
2
|
+
|
|
3
|
+
Capture durable facts discovered during work.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Date | Lesson | Source |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| YYYY-MM-DD | Short factual note | PR, issue, test, or file |
|
|
10
|
+
|
|
11
|
+
Good entries:
|
|
12
|
+
- Save future investigation.
|
|
13
|
+
- Explain non-obvious constraints.
|
|
14
|
+
- Point to evidence.
|
|
15
|
+
|
|
16
|
+
Avoid:
|
|
17
|
+
- Opinions without proof.
|
|
18
|
+
- Temporary task notes.
|
|
19
|
+
- Long narratives.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Module Index
|
|
2
|
+
|
|
3
|
+
List important modules and their responsibilities.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Path | Owns | Read When |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `src/...` | Main behavior or boundary | Task touches this area |
|
|
10
|
+
|
|
11
|
+
Keep entries short. Prefer stable boundaries over file-by-file detail.
|
|
12
|
+
|
|
13
|
+
Update when:
|
|
14
|
+
- A module is added, removed, or renamed.
|
|
15
|
+
- Ownership changes.
|
|
16
|
+
- A task repeatedly requires the same context.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Project Map
|
|
2
|
+
|
|
3
|
+
Describe the repository at a glance.
|
|
4
|
+
|
|
5
|
+
Fill in:
|
|
6
|
+
- Primary language/runtime:
|
|
7
|
+
- Package manager:
|
|
8
|
+
- Build command:
|
|
9
|
+
- Test command:
|
|
10
|
+
- Main entrypoints:
|
|
11
|
+
- Generated or vendored paths:
|
|
12
|
+
- Important conventions:
|
|
13
|
+
|
|
14
|
+
Agent notes:
|
|
15
|
+
- Keep this file factual.
|
|
16
|
+
- Link to files instead of restating long docs.
|
|
17
|
+
- Update when commands or entrypoints change.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Risk Register
|
|
2
|
+
|
|
3
|
+
Track areas where changes need extra care.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Area | Risk | Check |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `path/or/system` | What can break | Test or review needed |
|
|
10
|
+
|
|
11
|
+
Include:
|
|
12
|
+
- Security-sensitive code.
|
|
13
|
+
- Data migrations.
|
|
14
|
+
- Public APIs.
|
|
15
|
+
- Cross-cutting utilities.
|
|
16
|
+
- Flaky or expensive tests.
|
|
17
|
+
|
|
18
|
+
Keep risks current and specific.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Symbol Map
|
|
2
|
+
|
|
3
|
+
Track high-value names agents should find quickly.
|
|
4
|
+
|
|
5
|
+
Format:
|
|
6
|
+
|
|
7
|
+
| Symbol | Location | Notes |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `Name` | `path:line or path` | Role or caller guidance |
|
|
10
|
+
|
|
11
|
+
Include:
|
|
12
|
+
- Public APIs.
|
|
13
|
+
- Main classes/functions.
|
|
14
|
+
- Config objects.
|
|
15
|
+
- Cross-module types.
|
|
16
|
+
|
|
17
|
+
Do not list every symbol. Keep this navigational.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Task Routing
|
|
2
|
+
|
|
3
|
+
Use this map to choose the smallest useful context set.
|
|
4
|
+
|
|
5
|
+
Bug fix:
|
|
6
|
+
- Read `PROJECT_MAP.md`, `MODULE_INDEX.md`, `HOTSPOTS.md`, and related tests.
|
|
7
|
+
|
|
8
|
+
Feature:
|
|
9
|
+
- Read `PROJECT_MAP.md`, `MODULE_INDEX.md`, `DEPENDENCY_MAP.md`, and nearby examples.
|
|
10
|
+
|
|
11
|
+
Refactor:
|
|
12
|
+
- Read `DEPENDENCY_MAP.md`, `SYMBOL_MAP.md`, and affected callers.
|
|
13
|
+
|
|
14
|
+
Docs/config:
|
|
15
|
+
- Read `PROJECT_MAP.md` and the target files.
|
|
16
|
+
|
|
17
|
+
Before broad search:
|
|
18
|
+
- Check `DO_NOT_READ.md`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Token Budget
|
|
2
|
+
|
|
3
|
+
Use context deliberately.
|
|
4
|
+
|
|
5
|
+
Default read order:
|
|
6
|
+
1. `AGENTS.md`
|
|
7
|
+
2. `TASK_ROUTING.md`
|
|
8
|
+
3. Relevant map files
|
|
9
|
+
4. Target source and tests
|
|
10
|
+
|
|
11
|
+
Avoid:
|
|
12
|
+
- Generated output.
|
|
13
|
+
- Lockfiles unless dependency state matters.
|
|
14
|
+
- Large snapshots or fixtures unless failing behavior depends on them.
|
|
15
|
+
|
|
16
|
+
Escalate context only when the first pass leaves a concrete unknown.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const genericTemplateFiles: readonly ["AGENTS.md", "docs/ai-context/COMMUNICATION_MODE.md", "docs/ai-context/TASK_ROUTING.md", "docs/ai-context/MODULE_INDEX.md", "docs/ai-context/PROJECT_MAP.md", "docs/ai-context/RISK_REGISTER.md", "docs/ai-context/DEPENDENCY_MAP.md", "docs/ai-context/SYMBOL_MAP.md", "docs/ai-context/TOKEN_BUDGET.md", "docs/ai-context/DO_NOT_READ.md", "docs/ai-context/HOTSPOTS.md", "docs/ai-context/LESSONS_LEARNED.md", "docs/ai-context/CHANGE_LOG.md"];
|
|
2
|
+
export type GenericTemplateFile = (typeof genericTemplateFiles)[number];
|
|
3
|
+
export interface TemplateEntry {
|
|
4
|
+
path: GenericTemplateFile;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getGenericTemplateRoot(): string;
|
|
8
|
+
export declare function readGenericTemplate(file: GenericTemplateFile): Promise<TemplateEntry>;
|
|
9
|
+
export declare function readGenericTemplates(): Promise<TemplateEntry[]>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.genericTemplateFiles = void 0;
|
|
7
|
+
exports.getGenericTemplateRoot = getGenericTemplateRoot;
|
|
8
|
+
exports.readGenericTemplate = readGenericTemplate;
|
|
9
|
+
exports.readGenericTemplates = readGenericTemplates;
|
|
10
|
+
const promises_1 = require("node:fs/promises");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
exports.genericTemplateFiles = [
|
|
13
|
+
"AGENTS.md",
|
|
14
|
+
"docs/ai-context/COMMUNICATION_MODE.md",
|
|
15
|
+
"docs/ai-context/TASK_ROUTING.md",
|
|
16
|
+
"docs/ai-context/MODULE_INDEX.md",
|
|
17
|
+
"docs/ai-context/PROJECT_MAP.md",
|
|
18
|
+
"docs/ai-context/RISK_REGISTER.md",
|
|
19
|
+
"docs/ai-context/DEPENDENCY_MAP.md",
|
|
20
|
+
"docs/ai-context/SYMBOL_MAP.md",
|
|
21
|
+
"docs/ai-context/TOKEN_BUDGET.md",
|
|
22
|
+
"docs/ai-context/DO_NOT_READ.md",
|
|
23
|
+
"docs/ai-context/HOTSPOTS.md",
|
|
24
|
+
"docs/ai-context/LESSONS_LEARNED.md",
|
|
25
|
+
"docs/ai-context/CHANGE_LOG.md"
|
|
26
|
+
];
|
|
27
|
+
function getGenericTemplateRoot() {
|
|
28
|
+
return __dirname;
|
|
29
|
+
}
|
|
30
|
+
async function readGenericTemplate(file) {
|
|
31
|
+
const content = await (0, promises_1.readFile)(node_path_1.default.join(getGenericTemplateRoot(), file), "utf8");
|
|
32
|
+
return { path: file, content };
|
|
33
|
+
}
|
|
34
|
+
async function readGenericTemplates() {
|
|
35
|
+
return Promise.all(exports.genericTemplateFiles.map((file) => readGenericTemplate(file)));
|
|
36
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repo-context-center",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A ContextOps toolkit for AI coding agents.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"coding-agent",
|
|
8
|
+
"context",
|
|
9
|
+
"cli",
|
|
10
|
+
"repository"
|
|
11
|
+
],
|
|
12
|
+
"author": "Gökhan Yigit",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "commonjs",
|
|
15
|
+
"main": "./dist/cli/index.js",
|
|
16
|
+
"types": "./dist/cli/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"repo-context-center": "dist/cli/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-templates.js",
|
|
27
|
+
"prepublishOnly": "npm run build && npm test",
|
|
28
|
+
"test": "npm run build && node --test tests/*.test.js"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^24.13.2",
|
|
32
|
+
"typescript": "^5.8.0"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
}
|
|
37
|
+
}
|