thachvd-kit 1.0.32 → 1.0.34
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 -21
- package/README.md +49 -5
- package/bin/cli.js +1651 -1651
- package/bin/entry.js +89 -0
- package/bin/global.js +103 -0
- package/bin/policy.js +196 -0
- package/bin/upgrade.js +86 -0
- package/package.json +4 -4
package/bin/entry.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
snapshotPolicyFiles,
|
|
5
|
+
applyPolicyUpgrades
|
|
6
|
+
} = require('./policy');
|
|
7
|
+
const { upgradeProject } = require('./upgrade');
|
|
8
|
+
const { installGlobalPolicies } = require('./global');
|
|
9
|
+
|
|
10
|
+
function isInitInvocation(args) {
|
|
11
|
+
if (args.includes('--help') || args.includes('-h') || args.includes('--version') || args.includes('-v')) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const first = args[0];
|
|
15
|
+
return !first || first === 'init' || first.startsWith('-');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function runUpgrade(args) {
|
|
19
|
+
const results = upgradeProject(process.cwd(), { dryRun: args.includes('--dry-run') });
|
|
20
|
+
const changed = results.filter(result => result.changed);
|
|
21
|
+
if (changed.length === 0) {
|
|
22
|
+
console.log('No managed project policy changes needed.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const result of changed) {
|
|
26
|
+
console.log(`${result.dryRun ? 'WOULD UPDATE' : 'UPDATED'} ${result.path}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runGlobal(args) {
|
|
31
|
+
const homeDir = process.env.USERPROFILE || process.env.HOME;
|
|
32
|
+
if (!homeDir) {
|
|
33
|
+
console.error('Cannot determine home directory from USERPROFILE or HOME.');
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const antigravityOnly = args.includes('--antigravity-only');
|
|
39
|
+
const codexOnly = args.includes('--codex-only');
|
|
40
|
+
if (antigravityOnly && codexOnly) {
|
|
41
|
+
console.error('Choose at most one of --antigravity-only or --codex-only.');
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const results = installGlobalPolicies(homeDir, {
|
|
47
|
+
antigravityOnly,
|
|
48
|
+
codexOnly,
|
|
49
|
+
dryRun: args.includes('--dry-run')
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
for (const result of results) {
|
|
53
|
+
const status = result.changed ? (result.dryRun ? 'WOULD UPDATE' : 'UPDATED') : 'OK';
|
|
54
|
+
console.log(`${status} ${result.path}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (results.some(result => result.changed) && !args.includes('--dry-run')) {
|
|
58
|
+
console.log('Restart Antigravity/Codex sessions so the updated global instructions are reloaded.');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
const command = args[0];
|
|
64
|
+
|
|
65
|
+
if (command === 'upgrade') {
|
|
66
|
+
runUpgrade(args.slice(1));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (command === 'global') {
|
|
71
|
+
runGlobal(args.slice(1));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const shouldUpgradeGeneratedPolicy = isInitInvocation(args);
|
|
76
|
+
const beforeSnapshot = shouldUpgradeGeneratedPolicy ? snapshotPolicyFiles(process.cwd()) : null;
|
|
77
|
+
const force = args.includes('--yes') || args.includes('-y');
|
|
78
|
+
|
|
79
|
+
if (shouldUpgradeGeneratedPolicy) {
|
|
80
|
+
process.once('beforeExit', code => {
|
|
81
|
+
if (code !== 0 || (process.exitCode && process.exitCode !== 0)) return;
|
|
82
|
+
const changed = applyPolicyUpgrades(process.cwd(), beforeSnapshot, { force });
|
|
83
|
+
if (changed.length > 0) {
|
|
84
|
+
console.log(` OK Applied engineering quality policy to ${changed.length} generated context file(s)`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
require('./cli');
|
package/bin/global.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const START = '<!-- thachvd-kit:global-tool-routing:start -->';
|
|
5
|
+
const END = '<!-- thachvd-kit:global-tool-routing:end -->';
|
|
6
|
+
|
|
7
|
+
const GLOBAL_TOOL_ROUTING = `${START}
|
|
8
|
+
## AI Coding Tool Routing
|
|
9
|
+
|
|
10
|
+
Optimize tool usage for correctness and context efficiency.
|
|
11
|
+
|
|
12
|
+
### Core principle
|
|
13
|
+
|
|
14
|
+
Use the cheapest reliable source that can answer the question. Do not perform broad repository searches when structural tools can answer the question more precisely, and do not retrieve the same evidence twice once sufficient context is available.
|
|
15
|
+
|
|
16
|
+
This routing policy supersedes older blanket instructions such as “ALWAYS prefer MCP graph tools over grep/glob/file-search”. Graph-first is preferred for structural discovery, not for every search.
|
|
17
|
+
|
|
18
|
+
### Code discovery
|
|
19
|
+
|
|
20
|
+
- For unknown code ownership, symbols, callers/callees, architecture, dependencies, or impact analysis, prefer one available structural index such as \`codebase-memory-mcp\` or CodeGraph.
|
|
21
|
+
- If multiple structural indexes are available, choose one first. Do not query both for the same question unless the first result is insufficient or stale.
|
|
22
|
+
- Once the relevant file or symbol is known, read only the specific code needed for the task.
|
|
23
|
+
|
|
24
|
+
### Direct search
|
|
25
|
+
|
|
26
|
+
Use \`rg\`, \`grep\`, globbing, or direct file reads when they are the more precise and cheaper operation, including exact strings, error messages, config values, environment variables, TODO/FIXME markers, filenames, non-code files, and known narrow locations.
|
|
27
|
+
|
|
28
|
+
Avoid broad repository-wide text searches for structural questions.
|
|
29
|
+
|
|
30
|
+
### Shell output
|
|
31
|
+
|
|
32
|
+
Prefer RTK for verbose commands when it supports the command and preserves the information needed, especially git status/diff/log, tests, builds, package-manager commands, Docker, Kubernetes, and noisy linters.
|
|
33
|
+
|
|
34
|
+
Use raw command output when RTK is unavailable, incompatible, or raw output is specifically required for diagnosis.
|
|
35
|
+
|
|
36
|
+
### Context budget
|
|
37
|
+
|
|
38
|
+
Treat tool output as part of the model context budget. Narrow searches and commands before running them, avoid duplicate retrieval, and request only the relevant file, symbol, range, test, or log section.
|
|
39
|
+
|
|
40
|
+
Correctness takes priority over token savings. Never omit context required to make a safe and correct change.
|
|
41
|
+
${END}`;
|
|
42
|
+
|
|
43
|
+
function normalize(text) {
|
|
44
|
+
return String(text || '').replace(/\r\n/g, '\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function upsertManagedBlock(content, block = GLOBAL_TOOL_ROUTING) {
|
|
48
|
+
const text = normalize(content);
|
|
49
|
+
const start = text.indexOf(START);
|
|
50
|
+
const end = text.indexOf(END);
|
|
51
|
+
|
|
52
|
+
if (start >= 0 && end >= start) {
|
|
53
|
+
const after = end + END.length;
|
|
54
|
+
return `${text.slice(0, start)}${block}${text.slice(after)}`.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const prefix = text.trimEnd();
|
|
58
|
+
return `${prefix}${prefix ? '\n\n' : ''}${block}\n`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveCodexGlobalTarget(homeDir) {
|
|
62
|
+
const codexDir = path.join(homeDir, '.codex');
|
|
63
|
+
const overridePath = path.join(codexDir, 'AGENTS.override.md');
|
|
64
|
+
if (fs.existsSync(overridePath) && fs.statSync(overridePath).size > 0) return overridePath;
|
|
65
|
+
return path.join(codexDir, 'AGENTS.md');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function installGlobalRouting(filePath, options = {}) {
|
|
69
|
+
const dryRun = options.dryRun === true;
|
|
70
|
+
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
71
|
+
const updated = upsertManagedBlock(existing);
|
|
72
|
+
const changed = normalize(existing) !== updated;
|
|
73
|
+
|
|
74
|
+
if (changed && !dryRun) {
|
|
75
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
76
|
+
fs.writeFileSync(filePath, updated, 'utf8');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { path: filePath, changed, dryRun: changed && dryRun };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function installGlobalPolicies(homeDir, options = {}) {
|
|
83
|
+
const antigravityOnly = options.antigravityOnly === true;
|
|
84
|
+
const codexOnly = options.codexOnly === true;
|
|
85
|
+
if (antigravityOnly && codexOnly) {
|
|
86
|
+
throw new Error('Choose at most one of antigravityOnly or codexOnly.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const targets = [];
|
|
90
|
+
if (!codexOnly) targets.push(path.join(homeDir, '.gemini', 'GEMINI.md'));
|
|
91
|
+
if (!antigravityOnly) targets.push(resolveCodexGlobalTarget(homeDir));
|
|
92
|
+
return targets.map(filePath => installGlobalRouting(filePath, options));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
START,
|
|
97
|
+
END,
|
|
98
|
+
GLOBAL_TOOL_ROUTING,
|
|
99
|
+
upsertManagedBlock,
|
|
100
|
+
resolveCodexGlobalTarget,
|
|
101
|
+
installGlobalRouting,
|
|
102
|
+
installGlobalPolicies
|
|
103
|
+
};
|
package/bin/policy.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const POLICY_PATHS = [
|
|
5
|
+
'AGENTS.md',
|
|
6
|
+
'.cursorrules',
|
|
7
|
+
path.join('.agent', 'docs', 'conventions.md'),
|
|
8
|
+
path.join('.agent', 'docs', 'workflow.md'),
|
|
9
|
+
path.join('.agent', 'docs', 'getting-started.md'),
|
|
10
|
+
path.join('.agent', 'docs', 'tooling.md')
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const QUALITY_FLOOR_SECTION = `## Engineering Quality Floor
|
|
14
|
+
|
|
15
|
+
Prefer the smallest sufficient change, not the fewest lines of code.
|
|
16
|
+
|
|
17
|
+
A simpler implementation is acceptable only when it fully preserves:
|
|
18
|
+
|
|
19
|
+
- required behavior and acceptance criteria;
|
|
20
|
+
- existing product and architectural contracts;
|
|
21
|
+
- UX and accessibility behavior;
|
|
22
|
+
- security and data integrity;
|
|
23
|
+
- compatibility and expected performance;
|
|
24
|
+
- maintainability appropriate to the codebase.
|
|
25
|
+
|
|
26
|
+
Do not bypass an existing project abstraction merely because a lower-level primitive requires less code. Existing abstractions may encode product behavior and implicit requirements. Do not introduce speculative abstractions either; add complexity only when concrete requirements or codebase evidence justify it.`;
|
|
27
|
+
|
|
28
|
+
const AGENT_RULES_SECTION = `## Rules
|
|
29
|
+
|
|
30
|
+
- Respond in the user's language; keep code, identifiers, and code comments in English.
|
|
31
|
+
- State assumptions when the request is ambiguous.
|
|
32
|
+
- Prefer established project patterns and abstractions when they satisfy the required contract.
|
|
33
|
+
- Keep changes surgical, but never trade away required behavior, UX, accessibility, security, data integrity, compatibility, or maintainability just to reduce code.
|
|
34
|
+
- Do not split or reorganize code solely to satisfy a line-count target. Prefer cohesive modules with clear responsibilities; split only for a concrete cohesion, ownership, testability, or maintainability benefit.
|
|
35
|
+
- **Context retrieval**: use the cheapest source that can answer the question reliably. Read/search directly for a known local file or obvious local edit; use \`codebase-memory-mcp\` or another configured structural index for unknown ownership, symbol relationships, call paths, architecture, or impact; use \`context7\` for external API/docs uncertainty; use \`playwright\` for browser/UI verification. Avoid duplicate retrieval when one source already provides sufficient evidence.
|
|
36
|
+
- Prefer \`rtk\` by default for verbose shell commands such as git, tests, builds, package managers, Docker, and Kubernetes. Run the underlying command directly when RTK is unavailable or incompatible.
|
|
37
|
+
- Tests or equivalent verification are mandatory before claiming completion.`;
|
|
38
|
+
|
|
39
|
+
const CONVENTIONS_STANDARDS_SECTION = `## Current Standards
|
|
40
|
+
|
|
41
|
+
- Code comments and identifiers should be written in English.
|
|
42
|
+
- Keep changes scoped to the user request and preserve established local conventions.
|
|
43
|
+
- Prefer existing project abstractions when they satisfy the required contract; do not bypass them solely to save lines of code.
|
|
44
|
+
- Do not split or reorganize code solely to satisfy a line-count target. Prefer cohesive modules with clear responsibilities.
|
|
45
|
+
- Add abstractions only when concrete requirements or codebase evidence justify them.
|
|
46
|
+
- TODO: refine naming, formatting, folder, API, state, styling, and testing conventions from the real codebase.`;
|
|
47
|
+
|
|
48
|
+
const FAST_PATH_SECTION = `## Fast Path
|
|
49
|
+
|
|
50
|
+
Fast-path eligibility is based on risk and contract surface, not file count. Use it only when the change is localized, mechanically obvious, has no meaningful public API, schema, security, data-integrity, dependency, CI/release, or architectural risk, and has focused verification available. A two-file change such as code plus its focused test may still be fast-path; a one-file auth, payment, schema, concurrency, or other high-risk change is not.
|
|
51
|
+
|
|
52
|
+
State the narrow scope and verification before editing. If the task becomes ambiguous, introduces new behavior or architectural decisions, or its risk/contract surface grows, switch to brainstorming and planning.`;
|
|
53
|
+
|
|
54
|
+
const GETTING_STARTED_FAST_PATH_SECTION = `## Fast Path
|
|
55
|
+
|
|
56
|
+
Use a fast path when the change is localized, mechanically obvious, low-risk, and has focused verification. File count alone does not determine eligibility: code plus a focused test can still be trivial, while a one-file security, schema, payment, concurrency, or public-contract change requires the full workflow. If scope or risk grows, return to brainstorming and planning.`;
|
|
57
|
+
|
|
58
|
+
const TOOL_ROUTING_SECTION = `## Tool Routing
|
|
59
|
+
|
|
60
|
+
Use the cheapest source that can answer the question reliably and avoid retrieving the same evidence twice.
|
|
61
|
+
|
|
62
|
+
- Known local file or obvious local edit: read/search directly.
|
|
63
|
+
- Unknown ownership, symbol relationships, call paths, architecture, or impact: use \`codebase-memory-mcp\` or another configured structural index such as CodeGraph.
|
|
64
|
+
- External library/API uncertainty: use Context7.
|
|
65
|
+
- Browser/UI behavior: use Playwright.
|
|
66
|
+
- Noisy shell output: prefer RTK.
|
|
67
|
+
|
|
68
|
+
Structural tools are aids, not mandatory ceremony. If direct evidence is already sufficient, do not call extra MCP tools just because they are available.`;
|
|
69
|
+
|
|
70
|
+
function normalizeNewlines(text) {
|
|
71
|
+
return String(text || '').replace(/\r\n/g, '\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function replaceSection(markdown, heading, replacement) {
|
|
75
|
+
const text = normalizeNewlines(markdown);
|
|
76
|
+
const marker = `${heading}\n`;
|
|
77
|
+
const start = text.indexOf(marker);
|
|
78
|
+
if (start < 0) return text;
|
|
79
|
+
|
|
80
|
+
const searchFrom = start + marker.length;
|
|
81
|
+
const nextHeading = text.indexOf('\n## ', searchFrom);
|
|
82
|
+
const end = nextHeading >= 0 ? nextHeading + 1 : text.length;
|
|
83
|
+
const prefix = text.slice(0, start);
|
|
84
|
+
const suffix = text.slice(end).replace(/^\n+/, '');
|
|
85
|
+
return `${prefix}${replacement.trimEnd()}\n\n${suffix}`.replace(/\n{3,}/g, '\n\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function insertSectionBefore(markdown, beforeHeading, section) {
|
|
89
|
+
const text = normalizeNewlines(markdown);
|
|
90
|
+
const sectionHeading = section.split('\n', 1)[0];
|
|
91
|
+
if (text.includes(`${sectionHeading}\n`)) return text;
|
|
92
|
+
|
|
93
|
+
const marker = `${beforeHeading}\n`;
|
|
94
|
+
const index = text.indexOf(marker);
|
|
95
|
+
if (index < 0) return `${text.trimEnd()}\n\n${section.trimEnd()}\n`;
|
|
96
|
+
return `${text.slice(0, index)}${section.trimEnd()}\n\n${text.slice(index)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function replaceFastPathInline(markdown) {
|
|
100
|
+
return normalizeNewlines(markdown)
|
|
101
|
+
.replace(
|
|
102
|
+
'Questions and research do not edit product code. A simple fix may use a fast path only when it is one-file, unambiguous, and changes no behavior or contract. State the scope and verification before editing. If the scope grows, switch to the full Superpowers flow.',
|
|
103
|
+
'Questions and research do not edit product code. A fast path is allowed only when the change is localized, mechanically obvious, low-risk, and has focused verification; file count alone is not a gate. State the scope and verification before editing. If the task becomes ambiguous or its behavior, contract, or risk surface grows, switch to the full Superpowers flow.'
|
|
104
|
+
)
|
|
105
|
+
.replace(
|
|
106
|
+
'Questions and research do not edit product code. A simple fix may use a fast path only when it is one-file, unambiguous, and changes no behavior or contract. State the narrow scope and verification before editing. If the scope grows, return to brainstorming and planning.',
|
|
107
|
+
'Questions and research do not edit product code. A fast path is allowed only when the change is localized, mechanically obvious, low-risk, and has focused verification; file count alone is not a gate. State the narrow scope and verification before editing. If scope or risk grows, return to brainstorming and planning.'
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function upgradeAgentsMarkdown(markdown) {
|
|
112
|
+
let text = replaceFastPathInline(markdown);
|
|
113
|
+
text = insertSectionBefore(text, '## Rules', QUALITY_FLOOR_SECTION);
|
|
114
|
+
text = replaceSection(text, '## Rules', AGENT_RULES_SECTION);
|
|
115
|
+
return text.trimEnd() + '\n';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function upgradeConventionsMarkdown(markdown) {
|
|
119
|
+
let text = replaceSection(markdown, '## Current Standards', CONVENTIONS_STANDARDS_SECTION);
|
|
120
|
+
text = insertSectionBefore(text, '## Verification', QUALITY_FLOOR_SECTION);
|
|
121
|
+
return text.trimEnd() + '\n';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function upgradeWorkflowMarkdown(markdown) {
|
|
125
|
+
return replaceSection(markdown, '## Fast Path', FAST_PATH_SECTION).trimEnd() + '\n';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function upgradeGettingStartedMarkdown(markdown) {
|
|
129
|
+
return replaceSection(markdown, '## Fast Path', GETTING_STARTED_FAST_PATH_SECTION).trimEnd() + '\n';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function upgradeToolingMarkdown(markdown) {
|
|
133
|
+
const text = normalizeNewlines(markdown);
|
|
134
|
+
const targetHeading = text.includes('## Codebase Memory MCP\n')
|
|
135
|
+
? '## Codebase Memory MCP'
|
|
136
|
+
: '## codebase-memory-mcp';
|
|
137
|
+
return insertSectionBefore(text, targetHeading, TOOL_ROUTING_SECTION).trimEnd() + '\n';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function upgradeGeneratedContent(relativePath, content) {
|
|
141
|
+
const normalized = relativePath.split(path.sep).join('/');
|
|
142
|
+
if (normalized === 'AGENTS.md' || normalized === '.cursorrules') return upgradeAgentsMarkdown(content);
|
|
143
|
+
if (normalized.endsWith('/conventions.md')) return upgradeConventionsMarkdown(content);
|
|
144
|
+
if (normalized.endsWith('/workflow.md')) return upgradeWorkflowMarkdown(content);
|
|
145
|
+
if (normalized.endsWith('/getting-started.md')) return upgradeGettingStartedMarkdown(content);
|
|
146
|
+
if (normalized.endsWith('/tooling.md')) return upgradeToolingMarkdown(content);
|
|
147
|
+
return normalizeNewlines(content);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function snapshotPolicyFiles(rootDir) {
|
|
151
|
+
const snapshot = new Map();
|
|
152
|
+
for (const relativePath of POLICY_PATHS) {
|
|
153
|
+
const fullPath = path.join(rootDir, relativePath);
|
|
154
|
+
snapshot.set(relativePath, fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : null);
|
|
155
|
+
}
|
|
156
|
+
return snapshot;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function applyPolicyUpgrades(rootDir, beforeSnapshot, options = {}) {
|
|
160
|
+
const force = options.force === true;
|
|
161
|
+
const changed = [];
|
|
162
|
+
|
|
163
|
+
for (const relativePath of POLICY_PATHS) {
|
|
164
|
+
const fullPath = path.join(rootDir, relativePath);
|
|
165
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
166
|
+
|
|
167
|
+
const current = fs.readFileSync(fullPath, 'utf8');
|
|
168
|
+
const before = beforeSnapshot ? beforeSnapshot.get(relativePath) : null;
|
|
169
|
+
const wasWritten = force || before === null || before !== current;
|
|
170
|
+
if (!wasWritten) continue;
|
|
171
|
+
|
|
172
|
+
const upgraded = upgradeGeneratedContent(relativePath, current);
|
|
173
|
+
if (upgraded !== normalizeNewlines(current)) {
|
|
174
|
+
fs.writeFileSync(fullPath, upgraded, 'utf8');
|
|
175
|
+
changed.push(relativePath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return changed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
POLICY_PATHS,
|
|
184
|
+
QUALITY_FLOOR_SECTION,
|
|
185
|
+
AGENT_RULES_SECTION,
|
|
186
|
+
FAST_PATH_SECTION,
|
|
187
|
+
TOOL_ROUTING_SECTION,
|
|
188
|
+
upgradeGeneratedContent,
|
|
189
|
+
upgradeAgentsMarkdown,
|
|
190
|
+
upgradeConventionsMarkdown,
|
|
191
|
+
upgradeWorkflowMarkdown,
|
|
192
|
+
upgradeGettingStartedMarkdown,
|
|
193
|
+
upgradeToolingMarkdown,
|
|
194
|
+
snapshotPolicyFiles,
|
|
195
|
+
applyPolicyUpgrades
|
|
196
|
+
};
|
package/bin/upgrade.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const START = '<!-- thachvd-kit:project-policy:start -->';
|
|
5
|
+
const END = '<!-- thachvd-kit:project-policy:end -->';
|
|
6
|
+
|
|
7
|
+
const PROJECT_POLICY = `${START}
|
|
8
|
+
## Engineering Policy
|
|
9
|
+
|
|
10
|
+
Prefer the smallest sufficient change, not the fewest lines of code.
|
|
11
|
+
|
|
12
|
+
- Preserve required behavior, product and architectural contracts, UX/accessibility, security/data integrity, compatibility/performance, and maintainability before optimizing for simplicity.
|
|
13
|
+
- Reuse an existing project abstraction when it satisfies the required contract. Do not bypass it merely because a lower-level primitive is shorter.
|
|
14
|
+
- Avoid speculative abstractions and unrelated refactors.
|
|
15
|
+
- Do not split or reorganize code solely to satisfy a line-count target. Prefer cohesive modules and split only for a concrete cohesion, ownership, testability, or maintainability benefit.
|
|
16
|
+
- Fast-path eligibility is based on risk and contract surface, not file count. A code change plus its focused test can still be trivial; a one-file auth, payment, schema, concurrency, or public-contract change is not.
|
|
17
|
+
- Use the cheapest reliable context source. Read/search directly for known local code and exact text; use one structural index such as codebase-memory-mcp or CodeGraph for unknown ownership, call paths, architecture, or impact; avoid duplicate retrieval once sufficient evidence is available.
|
|
18
|
+
- Prefer RTK for verbose shell output when it preserves the information needed. Use raw output when required for diagnosis.
|
|
19
|
+
- Tests or equivalent verification are mandatory before claiming completion.
|
|
20
|
+
${END}`;
|
|
21
|
+
|
|
22
|
+
const TARGETS = [
|
|
23
|
+
'AGENTS.md',
|
|
24
|
+
path.join('.agent', 'docs', 'conventions.md'),
|
|
25
|
+
path.join('.agent', 'docs', 'workflow.md'),
|
|
26
|
+
path.join('.agent', 'docs', 'tooling.md')
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function normalize(text) {
|
|
30
|
+
return String(text || '').replace(/\r\n/g, '\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function removeKnownObsoleteDefaults(text) {
|
|
34
|
+
return normalize(text)
|
|
35
|
+
.replace(/^\s*- Maximum file length: 300 lines unless the existing project standard is stricter\.\s*\n?/gm, '')
|
|
36
|
+
.replace(/^\s*- Keep files under 300 lines unless the project already has a different standard[^\n]*\n?/gm, '')
|
|
37
|
+
.replace(/^\s*- \*\*MCP First\*\*:[^\n]*\n?/gm, '')
|
|
38
|
+
.replace(/^\s*- Use MCP first for code discovery and technical documentation;[^\n]*\n?/gm, '')
|
|
39
|
+
.replace(/Use it only for one-file, unambiguous changes with no behavior, API, schema, security, dependency, CI, workflow, or release contract change\.[^\n]*/g,
|
|
40
|
+
'Fast-path eligibility is based on risk and contract surface, not file count. Use it only for localized, mechanically obvious, low-risk changes with focused verification.')
|
|
41
|
+
.replace(/Use a fast path only for one-file, unambiguous changes with no behavior or contract change\.[^\n]*/g,
|
|
42
|
+
'Use a fast path only for localized, mechanically obvious, low-risk changes with focused verification; file count alone is not a gate.')
|
|
43
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function upsertManagedBlock(content) {
|
|
47
|
+
let text = removeKnownObsoleteDefaults(content);
|
|
48
|
+
const start = text.indexOf(START);
|
|
49
|
+
const end = text.indexOf(END);
|
|
50
|
+
|
|
51
|
+
if (start >= 0 && end >= start) {
|
|
52
|
+
const after = end + END.length;
|
|
53
|
+
return `${text.slice(0, start)}${PROJECT_POLICY}${text.slice(after)}`.replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const trimmed = text.trimEnd();
|
|
57
|
+
return `${trimmed}${trimmed ? '\n\n' : ''}${PROJECT_POLICY}\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function upgradeProject(rootDir, options = {}) {
|
|
61
|
+
const dryRun = options.dryRun === true;
|
|
62
|
+
const results = [];
|
|
63
|
+
|
|
64
|
+
for (const relativePath of TARGETS) {
|
|
65
|
+
const fullPath = path.join(rootDir, relativePath);
|
|
66
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
67
|
+
|
|
68
|
+
const existing = fs.readFileSync(fullPath, 'utf8');
|
|
69
|
+
const updated = upsertManagedBlock(existing);
|
|
70
|
+
const changed = normalize(existing) !== updated;
|
|
71
|
+
if (changed && !dryRun) fs.writeFileSync(fullPath, updated, 'utf8');
|
|
72
|
+
results.push({ path: relativePath, changed, dryRun: changed && dryRun });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
START,
|
|
80
|
+
END,
|
|
81
|
+
PROJECT_POLICY,
|
|
82
|
+
TARGETS,
|
|
83
|
+
removeKnownObsoleteDefaults,
|
|
84
|
+
upsertManagedBlock,
|
|
85
|
+
upgradeProject
|
|
86
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thachvd-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "Cross-agent project rules bootstrap kit for Codex, Antigravity, and Claude Code",
|
|
5
5
|
"bin": {
|
|
6
|
-
"thachvd-kit": "./bin/
|
|
6
|
+
"thachvd-kit": "./bin/entry.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"node": ">=16.7"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"test": "node test/cli.test.js",
|
|
19
|
-
"release:verify": "npm test && node --check bin/cli.js && npm pack --dry-run",
|
|
18
|
+
"test": "node test/cli.test.js && node test/policy.test.js && node test/upgrade.test.js && node test/global.test.js",
|
|
19
|
+
"release:verify": "npm test && node --check bin/cli.js && node --check bin/entry.js && node --check bin/policy.js && node --check bin/upgrade.js && node --check bin/global.js && npm pack --dry-run",
|
|
20
20
|
"preversion": "npm run release:verify",
|
|
21
21
|
"release:patch": "npm version patch",
|
|
22
22
|
"release:dry-run": "npm pack --dry-run",
|