mulch-cli 0.2.4 → 0.3.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/README.md +36 -4
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/compact.d.ts +2 -0
- package/dist/commands/compact.d.ts.map +1 -1
- package/dist/commands/compact.js +292 -4
- package/dist/commands/compact.js.map +1 -1
- package/dist/commands/diff.d.ts +11 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +170 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/prime.d.ts.map +1 -1
- package/dist/commands/prime.js +93 -16
- package/dist/commands/prime.js.map +1 -1
- package/dist/commands/record.d.ts +10 -0
- package/dist/commands/record.d.ts.map +1 -1
- package/dist/commands/record.js +155 -3
- package/dist/commands/record.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +10 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.js +5 -5
- package/dist/commands/sync.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/record-schema.d.ts +3 -0
- package/dist/schemas/record-schema.d.ts.map +1 -1
- package/dist/schemas/record-schema.js +1 -0
- package/dist/schemas/record-schema.js.map +1 -1
- package/dist/schemas/record.d.ts +1 -0
- package/dist/schemas/record.d.ts.map +1 -1
- package/dist/utils/budget.d.ts +33 -0
- package/dist/utils/budget.d.ts.map +1 -0
- package/dist/utils/budget.js +110 -0
- package/dist/utils/budget.js.map +1 -0
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +6 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/expertise.d.ts +23 -1
- package/dist/utils/expertise.d.ts.map +1 -1
- package/dist/utils/expertise.js +74 -2
- package/dist/utils/expertise.js.map +1 -1
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +47 -0
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/git.js +5 -5
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/lock.js +2 -2
- package/dist/utils/lock.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { isGitRepo } from "../utils/git.js";
|
|
4
|
+
import { getRecordSummary } from "../utils/format.js";
|
|
5
|
+
import { outputJson, outputJsonError } from "../utils/json-output.js";
|
|
6
|
+
export function parseExpertiseDiff(diffOutput) {
|
|
7
|
+
const lines = diffOutput.split("\n");
|
|
8
|
+
const entriesMap = new Map();
|
|
9
|
+
let currentDomain = null;
|
|
10
|
+
for (const line of lines) {
|
|
11
|
+
// Extract domain from 'diff --git' headers
|
|
12
|
+
if (line.startsWith("diff --git")) {
|
|
13
|
+
const match = line.match(/\.mulch\/expertise\/([^/]+)\.jsonl/);
|
|
14
|
+
if (match) {
|
|
15
|
+
currentDomain = match[1];
|
|
16
|
+
if (!entriesMap.has(currentDomain)) {
|
|
17
|
+
entriesMap.set(currentDomain, {
|
|
18
|
+
domain: currentDomain,
|
|
19
|
+
added: [],
|
|
20
|
+
removed: [],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Skip file metadata lines
|
|
27
|
+
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("@@")) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Parse added records (lines starting with '+')
|
|
31
|
+
if (line.startsWith("+") && currentDomain) {
|
|
32
|
+
const jsonStr = line.slice(1);
|
|
33
|
+
try {
|
|
34
|
+
const record = JSON.parse(jsonStr);
|
|
35
|
+
const entry = entriesMap.get(currentDomain);
|
|
36
|
+
if (entry) {
|
|
37
|
+
entry.added.push(record);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Skip lines that fail JSON.parse (context lines, hunk headers, etc.)
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Parse removed records (lines starting with '-')
|
|
46
|
+
if (line.startsWith("-") && currentDomain) {
|
|
47
|
+
const jsonStr = line.slice(1);
|
|
48
|
+
try {
|
|
49
|
+
const record = JSON.parse(jsonStr);
|
|
50
|
+
const entry = entriesMap.get(currentDomain);
|
|
51
|
+
if (entry) {
|
|
52
|
+
entry.removed.push(record);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Skip lines that fail JSON.parse (context lines, hunk headers, etc.)
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Return entries sorted by domain, filtered to non-empty
|
|
62
|
+
return [...entriesMap.values()]
|
|
63
|
+
.filter((entry) => entry.added.length > 0 || entry.removed.length > 0)
|
|
64
|
+
.sort((a, b) => a.domain.localeCompare(b.domain));
|
|
65
|
+
}
|
|
66
|
+
export function formatDiffOutput(entries, since) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push(`Expertise changes since ${since}`);
|
|
69
|
+
lines.push("");
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const changeCount = entry.added.length + entry.removed.length;
|
|
72
|
+
const plural = changeCount === 1 ? "change" : "changes";
|
|
73
|
+
lines.push(`${entry.domain} (${changeCount} ${plural}):`);
|
|
74
|
+
for (const record of entry.added) {
|
|
75
|
+
const summary = getRecordSummary(record);
|
|
76
|
+
lines.push(` + [${record.type}] ${record.id} ${summary}`);
|
|
77
|
+
}
|
|
78
|
+
for (const record of entry.removed) {
|
|
79
|
+
const summary = getRecordSummary(record);
|
|
80
|
+
lines.push(` - [${record.type}] ${record.id} ${summary}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
return lines.join("\n").trimEnd();
|
|
85
|
+
}
|
|
86
|
+
export function registerDiffCommand(program) {
|
|
87
|
+
program
|
|
88
|
+
.command("diff")
|
|
89
|
+
.description("Show expertise record changes since a git ref")
|
|
90
|
+
.option("--since <ref>", "git ref to diff against", "HEAD~1")
|
|
91
|
+
.action(async (options) => {
|
|
92
|
+
const jsonMode = program.opts().json === true;
|
|
93
|
+
const cwd = process.cwd();
|
|
94
|
+
if (!isGitRepo(cwd)) {
|
|
95
|
+
if (jsonMode) {
|
|
96
|
+
outputJsonError("diff", "Not in a git repository. Run this command from within a git repository.");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.error(chalk.red("Error: not in a git repository."));
|
|
100
|
+
}
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
let diffOutput;
|
|
106
|
+
try {
|
|
107
|
+
diffOutput = execFileSync("git", ["diff", options.since, "--", ".mulch/expertise/"], {
|
|
108
|
+
cwd,
|
|
109
|
+
encoding: "utf-8",
|
|
110
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ref doesn't exist or no changes, treat as empty diff
|
|
115
|
+
diffOutput = "";
|
|
116
|
+
}
|
|
117
|
+
const entries = parseExpertiseDiff(diffOutput);
|
|
118
|
+
if (entries.length === 0) {
|
|
119
|
+
if (jsonMode) {
|
|
120
|
+
outputJson({
|
|
121
|
+
success: true,
|
|
122
|
+
command: "diff",
|
|
123
|
+
since: options.since,
|
|
124
|
+
domains: [],
|
|
125
|
+
message: "No expertise changes found.",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log("No expertise changes found.");
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (jsonMode) {
|
|
134
|
+
outputJson({
|
|
135
|
+
success: true,
|
|
136
|
+
command: "diff",
|
|
137
|
+
since: options.since,
|
|
138
|
+
domains: entries,
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Plain text output with colors
|
|
143
|
+
console.log(chalk.bold(`\nExpertise changes since ${options.since}\n`));
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const changeCount = entry.added.length + entry.removed.length;
|
|
146
|
+
const plural = changeCount === 1 ? "change" : "changes";
|
|
147
|
+
console.log(chalk.cyan(`${entry.domain} (${changeCount} ${plural}):`));
|
|
148
|
+
for (const record of entry.added) {
|
|
149
|
+
const summary = getRecordSummary(record);
|
|
150
|
+
console.log(chalk.green(` + [${record.type}] ${record.id} ${summary}`));
|
|
151
|
+
}
|
|
152
|
+
for (const record of entry.removed) {
|
|
153
|
+
const summary = getRecordSummary(record);
|
|
154
|
+
console.log(chalk.red(` - [${record.type}] ${record.id} ${summary}`));
|
|
155
|
+
}
|
|
156
|
+
console.log("");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (jsonMode) {
|
|
161
|
+
outputJsonError("diff", err.message);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
165
|
+
}
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prime.d.ts","sourceRoot":"","sources":["../../src/commands/prime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"prime.d.ts","sourceRoot":"","sources":["../../src/commands/prime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiE5C,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoO3D"}
|
package/dist/commands/prime.js
CHANGED
|
@@ -3,9 +3,34 @@ import { Option } from "commander";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { readConfig, getExpertisePath } from "../utils/config.js";
|
|
5
5
|
import { readExpertiseFile, getFileModTime } from "../utils/expertise.js";
|
|
6
|
-
import { formatDomainExpertise, formatPrimeOutput, formatDomainExpertiseXml, formatPrimeOutputXml, formatDomainExpertisePlain, formatPrimeOutputPlain, formatDomainExpertiseCompact, formatPrimeOutputCompact, formatMcpOutput, } from "../utils/format.js";
|
|
6
|
+
import { formatDomainExpertise, formatPrimeOutput, formatDomainExpertiseXml, formatPrimeOutputXml, formatDomainExpertisePlain, formatPrimeOutputPlain, formatDomainExpertiseCompact, formatPrimeOutputCompact, formatMcpOutput, getSessionEndReminder, } from "../utils/format.js";
|
|
7
7
|
import { outputJsonError } from "../utils/json-output.js";
|
|
8
8
|
import { isGitRepo, getChangedFiles, filterByContext } from "../utils/git.js";
|
|
9
|
+
import { DEFAULT_BUDGET, applyBudget, formatBudgetSummary, } from "../utils/budget.js";
|
|
10
|
+
/**
|
|
11
|
+
* Produce a rough text representation of a record for token estimation.
|
|
12
|
+
* Uses a simple format similar to compact lines.
|
|
13
|
+
*/
|
|
14
|
+
function estimateRecordText(record) {
|
|
15
|
+
switch (record.type) {
|
|
16
|
+
case "convention":
|
|
17
|
+
return `[convention] ${record.content}`;
|
|
18
|
+
case "pattern": {
|
|
19
|
+
const files = record.files && record.files.length > 0 ? ` (${record.files.join(", ")})` : "";
|
|
20
|
+
return `[pattern] ${record.name}: ${record.description}${files}`;
|
|
21
|
+
}
|
|
22
|
+
case "failure":
|
|
23
|
+
return `[failure] ${record.description} -> ${record.resolution}`;
|
|
24
|
+
case "decision":
|
|
25
|
+
return `[decision] ${record.title}: ${record.rationale}`;
|
|
26
|
+
case "reference": {
|
|
27
|
+
const refFiles = record.files && record.files.length > 0 ? `: ${record.files.join(", ")}` : `: ${record.description}`;
|
|
28
|
+
return `[reference] ${record.name}${refFiles}`;
|
|
29
|
+
}
|
|
30
|
+
case "guide":
|
|
31
|
+
return `[guide] ${record.name}: ${record.description}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
9
34
|
export function registerPrimeCommand(program) {
|
|
10
35
|
program
|
|
11
36
|
.command("prime")
|
|
@@ -15,11 +40,15 @@ export function registerPrimeCommand(program) {
|
|
|
15
40
|
.option("-v, --verbose", "full output with section headers and recording instructions")
|
|
16
41
|
.option("--mcp", "output in MCP-compatible JSON format")
|
|
17
42
|
.option("--domain <domains...>", "domain(s) to include")
|
|
43
|
+
.option("--exclude-domain <domains...>", "domain(s) to exclude")
|
|
18
44
|
.addOption(new Option("--format <format>", "output format")
|
|
19
45
|
.choices(["markdown", "xml", "plain"])
|
|
20
46
|
.default("markdown"))
|
|
21
47
|
.option("--context", "filter records to only those relevant to changed files")
|
|
48
|
+
.option("--files <paths...>", "filter records to only those relevant to specified files")
|
|
22
49
|
.option("--export <path>", "export output to a file")
|
|
50
|
+
.option("--budget <tokens>", `token budget for output (default: ${DEFAULT_BUDGET})`)
|
|
51
|
+
.option("--no-limit", "disable token budget limit")
|
|
23
52
|
.action(async (domainsArg, options) => {
|
|
24
53
|
const jsonMode = program.opts().json === true;
|
|
25
54
|
try {
|
|
@@ -39,11 +68,25 @@ export function registerPrimeCommand(program) {
|
|
|
39
68
|
return;
|
|
40
69
|
}
|
|
41
70
|
}
|
|
42
|
-
const
|
|
71
|
+
const excluded = options.excludeDomain ?? [];
|
|
72
|
+
for (const d of excluded) {
|
|
73
|
+
if (!config.domains.includes(d)) {
|
|
74
|
+
if (jsonMode) {
|
|
75
|
+
outputJsonError("prime", `Excluded domain "${d}" not found in config. Available domains: ${config.domains.join(", ")}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.error(`Error: Excluded domain "${d}" not found in config. Available domains: ${config.domains.join(", ")}`);
|
|
79
|
+
}
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let targetDomains = unique.length > 0
|
|
43
85
|
? unique
|
|
44
86
|
: config.domains;
|
|
45
|
-
|
|
46
|
-
|
|
87
|
+
targetDomains = targetDomains.filter(d => !excluded.includes(d));
|
|
88
|
+
// Resolve changed files for --context or --files filtering
|
|
89
|
+
let filesToFilter;
|
|
47
90
|
if (options.context) {
|
|
48
91
|
const cwd = process.cwd();
|
|
49
92
|
if (!isGitRepo(cwd)) {
|
|
@@ -57,8 +100,8 @@ export function registerPrimeCommand(program) {
|
|
|
57
100
|
process.exitCode = 1;
|
|
58
101
|
return;
|
|
59
102
|
}
|
|
60
|
-
|
|
61
|
-
if (
|
|
103
|
+
filesToFilter = getChangedFiles(cwd, "HEAD~1");
|
|
104
|
+
if (filesToFilter.length === 0) {
|
|
62
105
|
if (jsonMode) {
|
|
63
106
|
outputJsonError("prime", "No changed files found. Nothing to filter by.");
|
|
64
107
|
}
|
|
@@ -68,34 +111,63 @@ export function registerPrimeCommand(program) {
|
|
|
68
111
|
return;
|
|
69
112
|
}
|
|
70
113
|
}
|
|
114
|
+
else if (options.files && options.files.length > 0) {
|
|
115
|
+
filesToFilter = options.files;
|
|
116
|
+
}
|
|
117
|
+
// Determine budget settings
|
|
118
|
+
const isMachineOutput = options.mcp === true || jsonMode;
|
|
119
|
+
const budgetEnabled = !isMachineOutput && options.noLimit !== true;
|
|
120
|
+
const budget = options.budget ? parseInt(options.budget, 10) : DEFAULT_BUDGET;
|
|
71
121
|
let output;
|
|
72
|
-
if (
|
|
73
|
-
// --json and --mcp produce the same structured output
|
|
122
|
+
if (isMachineOutput) {
|
|
123
|
+
// --json and --mcp produce the same structured output — no budget
|
|
74
124
|
const domains = [];
|
|
75
125
|
for (const domain of targetDomains) {
|
|
76
126
|
const filePath = getExpertisePath(domain);
|
|
77
127
|
let records = await readExpertiseFile(filePath);
|
|
78
|
-
if (
|
|
79
|
-
records = filterByContext(records,
|
|
128
|
+
if (filesToFilter) {
|
|
129
|
+
records = filterByContext(records, filesToFilter);
|
|
80
130
|
}
|
|
81
|
-
if (!
|
|
131
|
+
if (!filesToFilter || records.length > 0) {
|
|
82
132
|
domains.push({ domain, entry_count: records.length, records });
|
|
83
133
|
}
|
|
84
134
|
}
|
|
85
135
|
output = formatMcpOutput(domains);
|
|
86
136
|
}
|
|
87
137
|
else {
|
|
88
|
-
|
|
138
|
+
// Load all records per domain
|
|
139
|
+
const allDomainRecords = [];
|
|
140
|
+
const modTimes = new Map();
|
|
89
141
|
for (const domain of targetDomains) {
|
|
90
142
|
const filePath = getExpertisePath(domain);
|
|
91
143
|
let records = await readExpertiseFile(filePath);
|
|
92
|
-
if (
|
|
93
|
-
records = filterByContext(records,
|
|
144
|
+
if (filesToFilter) {
|
|
145
|
+
records = filterByContext(records, filesToFilter);
|
|
94
146
|
if (records.length === 0)
|
|
95
147
|
continue;
|
|
96
148
|
}
|
|
149
|
+
allDomainRecords.push({ domain, records });
|
|
97
150
|
const lastUpdated = await getFileModTime(filePath);
|
|
98
|
-
|
|
151
|
+
modTimes.set(domain, lastUpdated);
|
|
152
|
+
}
|
|
153
|
+
// Apply budget filtering
|
|
154
|
+
let domainRecordsToFormat;
|
|
155
|
+
let droppedCount = 0;
|
|
156
|
+
let droppedDomainCount = 0;
|
|
157
|
+
if (budgetEnabled) {
|
|
158
|
+
const result = applyBudget(allDomainRecords, budget, (record) => estimateRecordText(record));
|
|
159
|
+
domainRecordsToFormat = result.kept;
|
|
160
|
+
droppedCount = result.droppedCount;
|
|
161
|
+
droppedDomainCount = result.droppedDomainCount;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
domainRecordsToFormat = allDomainRecords;
|
|
165
|
+
}
|
|
166
|
+
// Format domain sections
|
|
167
|
+
const domainSections = [];
|
|
168
|
+
for (const { domain, records } of domainRecordsToFormat) {
|
|
169
|
+
const lastUpdated = modTimes.get(domain) ?? null;
|
|
170
|
+
if (options.verbose || options.full || format !== "markdown") {
|
|
99
171
|
switch (format) {
|
|
100
172
|
case "xml":
|
|
101
173
|
domainSections.push(formatDomainExpertiseXml(domain, records, lastUpdated));
|
|
@@ -114,7 +186,7 @@ export function registerPrimeCommand(program) {
|
|
|
114
186
|
domainSections.push(formatDomainExpertiseCompact(domain, records, lastUpdated));
|
|
115
187
|
}
|
|
116
188
|
}
|
|
117
|
-
if (options.verbose || format !== "markdown") {
|
|
189
|
+
if (options.verbose || options.full || format !== "markdown") {
|
|
118
190
|
switch (format) {
|
|
119
191
|
case "xml":
|
|
120
192
|
output = formatPrimeOutputXml(domainSections);
|
|
@@ -130,6 +202,11 @@ export function registerPrimeCommand(program) {
|
|
|
130
202
|
else {
|
|
131
203
|
output = formatPrimeOutputCompact(domainSections);
|
|
132
204
|
}
|
|
205
|
+
// Append truncation summary before session reminder
|
|
206
|
+
if (droppedCount > 0) {
|
|
207
|
+
output += "\n\n" + formatBudgetSummary(droppedCount, droppedDomainCount);
|
|
208
|
+
}
|
|
209
|
+
output += "\n\n" + getSessionEndReminder(format);
|
|
133
210
|
}
|
|
134
211
|
if (options.export) {
|
|
135
212
|
await writeFile(options.export, output + "\n", "utf-8");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prime.js","sourceRoot":"","sources":["../../src/commands/prime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAW,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,eAAe,
|
|
1
|
+
{"version":3,"file":"prime.js","sourceRoot":"","sources":["../../src/commands/prime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Process records from stdin (JSON single object or array)
|
|
4
|
+
* Validates, dedups, and appends with file locking
|
|
5
|
+
*/
|
|
6
|
+
export declare function processStdinRecords(domain: string, jsonMode: boolean, force: boolean, stdinData?: string, cwd?: string): Promise<{
|
|
7
|
+
created: number;
|
|
8
|
+
updated: number;
|
|
9
|
+
skipped: number;
|
|
10
|
+
errors: string[];
|
|
11
|
+
}>;
|
|
2
12
|
export declare function registerRecordCommand(program: Command): void;
|
|
3
13
|
//# sourceMappingURL=record.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/commands/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../src/commands/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAsB5C;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA+FlF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6b5D"}
|
package/dist/commands/record.js
CHANGED
|
@@ -7,6 +7,92 @@ import { appendRecord, readExpertiseFile, writeExpertiseFile, findDuplicate, } f
|
|
|
7
7
|
import { withFileLock } from "../utils/lock.js";
|
|
8
8
|
import { recordSchema } from "../schemas/record-schema.js";
|
|
9
9
|
import { outputJson, outputJsonError } from "../utils/json-output.js";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
/**
|
|
12
|
+
* Process records from stdin (JSON single object or array)
|
|
13
|
+
* Validates, dedups, and appends with file locking
|
|
14
|
+
*/
|
|
15
|
+
export async function processStdinRecords(domain, jsonMode, force, stdinData, cwd) {
|
|
16
|
+
const config = await readConfig(cwd);
|
|
17
|
+
if (!config.domains.includes(domain)) {
|
|
18
|
+
throw new Error(`Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`);
|
|
19
|
+
}
|
|
20
|
+
// Read stdin (or use provided data for testing)
|
|
21
|
+
const inputData = stdinData ?? readFileSync(0, "utf-8");
|
|
22
|
+
let inputRecords;
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(inputData);
|
|
25
|
+
inputRecords = Array.isArray(parsed) ? parsed : [parsed];
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
throw new Error(`Failed to parse JSON from stdin: ${err instanceof Error ? err.message : String(err)}`);
|
|
29
|
+
}
|
|
30
|
+
// Validate each record against schema
|
|
31
|
+
const ajv = new Ajv();
|
|
32
|
+
const validate = ajv.compile(recordSchema);
|
|
33
|
+
const errors = [];
|
|
34
|
+
const validRecords = [];
|
|
35
|
+
for (let i = 0; i < inputRecords.length; i++) {
|
|
36
|
+
const record = inputRecords[i];
|
|
37
|
+
// Ensure recorded_at and classification are set
|
|
38
|
+
if (typeof record === "object" && record !== null) {
|
|
39
|
+
if (!("recorded_at" in record)) {
|
|
40
|
+
record.recorded_at = new Date().toISOString();
|
|
41
|
+
}
|
|
42
|
+
if (!("classification" in record)) {
|
|
43
|
+
record.classification = "tactical";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!validate(record)) {
|
|
47
|
+
const validationErrors = (validate.errors ?? [])
|
|
48
|
+
.map((err) => `${err.instancePath} ${err.message}`)
|
|
49
|
+
.join("; ");
|
|
50
|
+
errors.push(`Record ${i}: ${validationErrors}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
validRecords.push(record);
|
|
54
|
+
}
|
|
55
|
+
if (validRecords.length === 0) {
|
|
56
|
+
return { created: 0, updated: 0, skipped: 0, errors };
|
|
57
|
+
}
|
|
58
|
+
// Process valid records with file locking
|
|
59
|
+
const filePath = getExpertisePath(domain, cwd);
|
|
60
|
+
let created = 0;
|
|
61
|
+
let updated = 0;
|
|
62
|
+
let skipped = 0;
|
|
63
|
+
await withFileLock(filePath, async () => {
|
|
64
|
+
const existing = await readExpertiseFile(filePath);
|
|
65
|
+
let currentRecords = [...existing];
|
|
66
|
+
for (const record of validRecords) {
|
|
67
|
+
const dup = findDuplicate(currentRecords, record);
|
|
68
|
+
if (dup && !force) {
|
|
69
|
+
const isNamed = record.type === "pattern" ||
|
|
70
|
+
record.type === "decision" ||
|
|
71
|
+
record.type === "reference" ||
|
|
72
|
+
record.type === "guide";
|
|
73
|
+
if (isNamed) {
|
|
74
|
+
// Upsert: replace in place
|
|
75
|
+
currentRecords[dup.index] = record;
|
|
76
|
+
updated++;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Exact match: skip
|
|
80
|
+
skipped++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// New record: append
|
|
85
|
+
currentRecords.push(record);
|
|
86
|
+
created++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Write all changes at once
|
|
90
|
+
if (created > 0 || updated > 0) {
|
|
91
|
+
await writeExpertiseFile(filePath, currentRecords);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return { created, updated, skipped, errors };
|
|
95
|
+
}
|
|
10
96
|
export function registerRecordCommand(program) {
|
|
11
97
|
program
|
|
12
98
|
.command("record")
|
|
@@ -14,8 +100,7 @@ export function registerRecordCommand(program) {
|
|
|
14
100
|
.argument("[content]", "record content")
|
|
15
101
|
.description("Record an expertise record")
|
|
16
102
|
.addOption(new Option("--type <type>", "record type")
|
|
17
|
-
.choices(["convention", "pattern", "failure", "decision", "reference", "guide"])
|
|
18
|
-
.makeOptionMandatory())
|
|
103
|
+
.choices(["convention", "pattern", "failure", "decision", "reference", "guide"]))
|
|
19
104
|
.addOption(new Option("--classification <classification>", "classification level")
|
|
20
105
|
.choices(["foundational", "tactical", "observational"])
|
|
21
106
|
.default("tactical"))
|
|
@@ -29,11 +114,65 @@ export function registerRecordCommand(program) {
|
|
|
29
114
|
.option("--evidence-commit <commit>", "evidence: commit hash")
|
|
30
115
|
.option("--evidence-issue <issue>", "evidence: issue reference")
|
|
31
116
|
.option("--evidence-file <file>", "evidence: file path")
|
|
117
|
+
.option("--evidence-bead <bead>", "evidence: bead ID")
|
|
32
118
|
.option("--relates-to <ids>", "comma-separated record IDs this relates to")
|
|
33
119
|
.option("--supersedes <ids>", "comma-separated record IDs this supersedes")
|
|
34
120
|
.option("--force", "force recording even if duplicate exists")
|
|
121
|
+
.option("--stdin", "read JSON record(s) from stdin (single object or array)")
|
|
35
122
|
.action(async (domain, content, options) => {
|
|
36
123
|
const jsonMode = program.opts().json === true;
|
|
124
|
+
// Handle --stdin mode
|
|
125
|
+
if (options.stdin === true) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await processStdinRecords(domain, jsonMode, options.force === true);
|
|
128
|
+
if (result.errors.length > 0) {
|
|
129
|
+
if (jsonMode) {
|
|
130
|
+
outputJsonError("record", `Validation errors: ${result.errors.join("; ")}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.error(chalk.red("Validation errors:"));
|
|
134
|
+
for (const error of result.errors) {
|
|
135
|
+
console.error(chalk.red(` ${error}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (jsonMode) {
|
|
140
|
+
outputJson({
|
|
141
|
+
success: result.errors.length === 0 || result.created + result.updated > 0,
|
|
142
|
+
command: "record",
|
|
143
|
+
domain,
|
|
144
|
+
created: result.created,
|
|
145
|
+
updated: result.updated,
|
|
146
|
+
skipped: result.skipped,
|
|
147
|
+
errors: result.errors,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
if (result.created > 0) {
|
|
152
|
+
console.log(chalk.green(`✔ Created ${result.created} record(s) in ${domain}`));
|
|
153
|
+
}
|
|
154
|
+
if (result.updated > 0) {
|
|
155
|
+
console.log(chalk.green(`✔ Updated ${result.updated} record(s) in ${domain}`));
|
|
156
|
+
}
|
|
157
|
+
if (result.skipped > 0) {
|
|
158
|
+
console.log(chalk.yellow(`Skipped ${result.skipped} duplicate(s) in ${domain}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (result.errors.length > 0 && result.created + result.updated === 0) {
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
if (jsonMode) {
|
|
167
|
+
outputJsonError("record", err instanceof Error ? err.message : String(err));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
171
|
+
}
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
37
176
|
const config = await readConfig();
|
|
38
177
|
if (!config.domains.includes(domain)) {
|
|
39
178
|
if (jsonMode) {
|
|
@@ -46,12 +185,23 @@ export function registerRecordCommand(program) {
|
|
|
46
185
|
process.exitCode = 1;
|
|
47
186
|
return;
|
|
48
187
|
}
|
|
188
|
+
// Validate --type is provided for non-stdin mode
|
|
189
|
+
if (!options.type) {
|
|
190
|
+
if (jsonMode) {
|
|
191
|
+
outputJsonError("record", "--type is required (convention, pattern, failure, decision, reference, guide)");
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
console.error(chalk.red("Error: --type is required (convention, pattern, failure, decision, reference, guide)"));
|
|
195
|
+
}
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
49
199
|
const recordType = options.type;
|
|
50
200
|
const classification = options.classification ?? "tactical";
|
|
51
201
|
const recordedAt = new Date().toISOString();
|
|
52
202
|
// Build evidence if any evidence option is provided
|
|
53
203
|
let evidence;
|
|
54
|
-
if (options.evidenceCommit || options.evidenceIssue || options.evidenceFile) {
|
|
204
|
+
if (options.evidenceCommit || options.evidenceIssue || options.evidenceFile || options.evidenceBead) {
|
|
55
205
|
evidence = {};
|
|
56
206
|
if (options.evidenceCommit)
|
|
57
207
|
evidence.commit = options.evidenceCommit;
|
|
@@ -59,6 +209,8 @@ export function registerRecordCommand(program) {
|
|
|
59
209
|
evidence.issue = options.evidenceIssue;
|
|
60
210
|
if (options.evidenceFile)
|
|
61
211
|
evidence.file = options.evidenceFile;
|
|
212
|
+
if (options.evidenceBead)
|
|
213
|
+
evidence.bead = options.evidenceBead;
|
|
62
214
|
}
|
|
63
215
|
const tags = typeof options.tags === "string"
|
|
64
216
|
? options.tags
|