mulch-cli 0.3.0 → 0.4.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 +20 -3
- package/dist/cli.js +1 -1
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +10 -3
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/record.d.ts +1 -1
- package/dist/commands/record.d.ts.map +1 -1
- package/dist/commands/record.js +251 -61
- package/dist/commands/record.js.map +1 -1
- package/dist/commands/setup.d.ts +2 -2
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +30 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/schemas/record-schema.d.ts +12 -12
- package/dist/schemas/record-schema.js +1 -1
- package/dist/schemas/record-schema.js.map +1 -1
- package/dist/utils/bm25.d.ts +39 -0
- package/dist/utils/bm25.d.ts.map +1 -0
- package/dist/utils/bm25.js +171 -0
- package/dist/utils/bm25.js.map +1 -0
- package/dist/utils/expertise.d.ts +4 -0
- package/dist/utils/expertise.d.ts.map +1 -1
- package/dist/utils/expertise.js +7 -16
- package/dist/utils/expertise.js.map +1 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +50 -12
- package/dist/utils/format.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,12 +68,12 @@ Everything is git-tracked. Clone a repo and your agents immediately have the pro
|
|
|
68
68
|
|---------|-------------|
|
|
69
69
|
| `mulch init` | Initialize `.mulch/` in the current project |
|
|
70
70
|
| `mulch add <domain>` | Add a new expertise domain |
|
|
71
|
-
| `mulch record <domain> --type <type>` | Record an expertise record (`--tags`, `--force`, `--relates-to`, `--supersedes`, `--stdin`, `--evidence-bead`) |
|
|
71
|
+
| `mulch record <domain> --type <type>` | Record an expertise record (`--tags`, `--force`, `--relates-to`, `--supersedes`, `--batch`, `--stdin`, `--dry-run`, `--evidence-bead`) |
|
|
72
72
|
| `mulch edit <domain> <id>` | Edit an existing record by ID or 1-based index |
|
|
73
73
|
| `mulch delete <domain> <id>` | Delete a record by ID or 1-based index |
|
|
74
74
|
| `mulch query [domain]` | Query expertise (use `--all` for all domains) |
|
|
75
75
|
| `mulch prime [domains...]` | Output AI-optimized expertise context (`--budget`, `--no-limit`, `--context`, `--files`, `--exclude-domain`, `--format`, `--export`) |
|
|
76
|
-
| `mulch search [query]` | Search records across domains (`--domain`, `--type`, `--tag` filters) |
|
|
76
|
+
| `mulch search [query]` | Search records across domains with BM25 ranking (`--domain`, `--type`, `--tag` filters) |
|
|
77
77
|
| `mulch compact [domain]` | Analyze compaction candidates or apply a compaction (`--analyze`, `--auto`, `--apply`, `--dry-run`, `--min-group`, `--max-records`) |
|
|
78
78
|
| `mulch diff [ref]` | Show expertise changes between git refs (`mulch diff HEAD~3`, `mulch diff main..feature`) |
|
|
79
79
|
| `mulch status` | Show expertise freshness and counts (`--json` for health metrics) |
|
|
@@ -97,7 +97,7 @@ Everything is git-tracked. Clone a repo and your agents immediately have the pro
|
|
|
97
97
|
| `reference` | name, description | Key files, endpoints, or resources worth remembering |
|
|
98
98
|
| `guide` | name, description | Step-by-step procedures for recurring tasks |
|
|
99
99
|
|
|
100
|
-
All records support optional `--classification` (foundational / tactical / observational), evidence flags (`--evidence-commit`, `--evidence-issue`, `--evidence-file`), `--tags`, `--relates-to`, and `--supersedes` for linking.
|
|
100
|
+
All records support optional `--classification` (foundational / tactical / observational), evidence flags (`--evidence-commit`, `--evidence-issue`, `--evidence-file`), `--tags`, `--relates-to`, and `--supersedes` for linking. Cross-domain references use `domain:mx-hash` format (e.g., `--relates-to api:mx-abc123`).
|
|
101
101
|
|
|
102
102
|
## Example Output
|
|
103
103
|
|
|
@@ -161,6 +161,23 @@ Locks ensure correctness automatically. If two agents record to the same domain
|
|
|
161
161
|
|
|
162
162
|
Each agent works in its own git worktree. On merge, `merge=union` combines all JSONL lines. Run `mulch doctor --fix` after merge to deduplicate if needed.
|
|
163
163
|
|
|
164
|
+
### Batch recording
|
|
165
|
+
|
|
166
|
+
For recording multiple records atomically (e.g., at session end), use `--batch` or `--stdin`:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# From a JSON file (single object or array of objects)
|
|
170
|
+
mulch record api --batch records.json
|
|
171
|
+
|
|
172
|
+
# From stdin
|
|
173
|
+
echo '[{"type":"convention","content":"Use UTC timestamps"}]' | mulch record api --stdin
|
|
174
|
+
|
|
175
|
+
# Preview first
|
|
176
|
+
mulch record api --batch records.json --dry-run
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Batch recording uses file locking — safe for concurrent use. Invalid records are skipped with errors; valid records in the same batch still succeed.
|
|
180
|
+
|
|
164
181
|
**Maintenance during swarm work**:
|
|
165
182
|
|
|
166
183
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyLpC,wBAAsB,UAAU,CAAC,OAAO,EAAE;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiIhB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyB7D"}
|
package/dist/commands/onboard.js
CHANGED
|
@@ -13,6 +13,7 @@ mulch prime
|
|
|
13
13
|
\`\`\`
|
|
14
14
|
|
|
15
15
|
This injects project-specific conventions, patterns, decisions, and other learnings into your context.
|
|
16
|
+
Use \`mulch prime --files src/foo.ts\` to load only records relevant to specific files.
|
|
16
17
|
|
|
17
18
|
**Before completing your task**, review your work for insights worth preserving — conventions discovered,
|
|
18
19
|
patterns applied, failures encountered, or decisions made — and record them:
|
|
@@ -20,19 +21,25 @@ patterns applied, failures encountered, or decisions made — and record them:
|
|
|
20
21
|
mulch record <domain> --type <convention|pattern|failure|decision|reference|guide> --description "..."
|
|
21
22
|
\`\`\`
|
|
22
23
|
|
|
24
|
+
Link evidence when available: \`--evidence-commit <sha>\`, \`--evidence-bead <id>\`
|
|
25
|
+
|
|
23
26
|
Run \`mulch status\` to check domain health and entry counts.
|
|
24
27
|
Run \`mulch --help\` for full usage.
|
|
25
28
|
Mulch write commands use file locking and atomic writes — multiple agents can safely record to the same domain concurrently.
|
|
26
29
|
|
|
27
30
|
### Before You Finish
|
|
28
31
|
|
|
29
|
-
1.
|
|
32
|
+
1. Discover what to record:
|
|
33
|
+
\`\`\`bash
|
|
34
|
+
mulch learn
|
|
35
|
+
\`\`\`
|
|
36
|
+
2. Store insights from this work session:
|
|
30
37
|
\`\`\`bash
|
|
31
38
|
mulch record <domain> --type <convention|pattern|failure|decision|reference|guide> --description "..."
|
|
32
39
|
\`\`\`
|
|
33
|
-
|
|
40
|
+
3. Validate and commit:
|
|
34
41
|
\`\`\`bash
|
|
35
|
-
mulch
|
|
42
|
+
mulch sync
|
|
36
43
|
\`\`\`
|
|
37
44
|
`;
|
|
38
45
|
const LEGACY_HEADER = "## Project Expertise (Mulch)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,MAAM,eAAe,GAAG
|
|
1
|
+
{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EACL,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,oBAAoB,EACpB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCvB,CAAC;AAEF,MAAM,aAAa,GAAG,8BAA8B,CAAC;AACrD,MAAM,WAAW,GAAG,8EAA8E,CAAC;AAEnG,SAAS,UAAU,CAAC,QAA4B;IAC9C,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,kDAAkD;IAClD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAQD,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAe,EAAE,UAAkB;IAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACjD,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,OAAO,CAAC;IAErC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAExD,IAAI,MAAc,CAAC;IACnB,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;QACnB,2CAA2C;QAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACvE,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;YACrB,MAAM,GAAG,SAAS,GAAG,CAAC,CAAC;YACvB,4BAA4B;YAC5B,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC3D,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC1B,CAAC;IACH,CAAC;SAAM,CAAC;QACN,oEAAoE;QACpE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAExC,OAAO,MAAM,GAAG,UAAU,GAAG,KAAK,CAAC;AACrC,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe,EAAE,cAAsB;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnD,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,CACrC,QAAQ,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,8BAA8B;IAClE,MAAM,CACP,CAAC;IAEF,OAAO,aAAa,CAAC,IAAI,EAAE,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,GAAW;IAC7C,MAAM,UAAU,GAAG;QACjB,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE;QACvD,EAAE,QAAQ,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE;QAC1E,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE;KACxD,CAAC;IAEF,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAChD,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,GAAW;IAI1C,MAAM,WAAW,GAAG,MAAM,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAEpD,kFAAkF;IAClF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;YACtB,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;SACjC,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO;YACL,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;YAC7E,UAAU,EAAE,EAAE;SACf,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAC9D,OAAO;QACL,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE;QACrF,UAAU,EAAE,EAAE;KACf,CAAC;AACJ,CAAC;AAYD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAMhC;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAE9C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrC,OAAO;IACT,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAE5D,gCAAgC;IAChC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,IAAI,MAAqB,CAAC;QAE1B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,GAAG,eAAe,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACrD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;YAC1E,CAAC;iBAAM,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,MAAM,GAAG,QAAQ,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,eAAe,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,UAAU,CAAC;gBACT,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,SAAS;gBAClB,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,MAAM;aACP,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAA2B;gBACvC,aAAa,EAAE,qCAAqC,MAAM,CAAC,QAAQ,GAAG;gBACtE,UAAU,EAAE,oBAAoB,MAAM,CAAC,QAAQ,iBAAiB;gBAChE,QAAQ,EAAE,oBAAoB,MAAM,CAAC,QAAQ,gDAAgD;gBAC7F,MAAM,EAAE,oBAAoB,MAAM,CAAC,QAAQ,qEAAqE;aACjH,CAAC;YACF,MAAM,MAAM,GAA0C;gBACpD,aAAa,EAAE,KAAK,CAAC,MAAM;gBAC3B,UAAU,EAAE,KAAK,CAAC,KAAK;gBACvB,QAAQ,EAAE,KAAK,CAAC,MAAM;gBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3D,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,yCAAyC,KAAK,EAAE,CAAC,CAC/D,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO;IACT,CAAC;IAED,aAAa;IACb,IAAI,MAAqB,CAAC;IAE1B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,kBAAkB;QAClB,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7D,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAErD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,mBAAmB;YACnB,IAAI,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;gBACvC,MAAM,GAAG,YAAY,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,yBAAyB;gBACzB,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;gBAC9D,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACrB,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;gBACjD,CAAC;gBACD,MAAM,GAAG,SAAS,CAAC;YACrB,CAAC;QACH,CAAC;aAAM,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,yBAAyB;YACzB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC;YACtE,MAAM,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;YAChD,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,0BAA0B;YAC1B,MAAM,SAAS,CACb,MAAM,CAAC,IAAI,EACX,OAAO,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,cAAc,GAAG,IAAI,EAClD,OAAO,CACR,CAAC;YACF,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,UAAU,CAAC;YACT,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,SAAS;YAClB,IAAI,EAAE,MAAM,CAAC,QAAQ;YACrB,MAAM;SACP,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,GAA2B;YACvC,OAAO,EAAE,uCAAuC,MAAM,CAAC,QAAQ,GAAG;YAClE,QAAQ,EAAE,wCAAwC,MAAM,CAAC,QAAQ,GAAG;YACpE,OAAO,EAAE,uCAAuC,MAAM,CAAC,QAAQ,GAAG;YAClE,QAAQ,EAAE,yDAAyD,MAAM,CAAC,QAAQ,GAAG;YACrF,UAAU,EAAE,oBAAoB,MAAM,CAAC,QAAQ,0CAA0C;SAC1F,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,yCAAyC,KAAK,EAAE,CAAC,CAC/D,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CACV,2EAA2E,CAC5E;SACA,MAAM,CAAC,UAAU,EAAE,oDAAoD,CAAC;SACxE,MAAM,CACL,uBAAuB,EACvB,yDAAyD,CAC1D;SACA,MAAM,CAAC,SAAS,EAAE,yDAAyD,CAAC;SAC5E,MAAM,CAAC,KAAK,EAAE,OAAiE,EAAE,EAAE;QAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,QAAQ,EAAE,CAAC;gBACb,eAAe,CAAC,SAAS,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,UAAW,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACpD,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { Command } from "commander";
|
|
|
3
3
|
* Process records from stdin (JSON single object or array)
|
|
4
4
|
* Validates, dedups, and appends with file locking
|
|
5
5
|
*/
|
|
6
|
-
export declare function processStdinRecords(domain: string, jsonMode: boolean, force: boolean, stdinData?: string, cwd?: string): Promise<{
|
|
6
|
+
export declare function processStdinRecords(domain: string, jsonMode: boolean, force: boolean, dryRun: boolean, stdinData?: string, cwd?: string): Promise<{
|
|
7
7
|
created: number;
|
|
8
8
|
updated: number;
|
|
9
9
|
skipped: number;
|
|
@@ -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;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,
|
|
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,MAAM,EAAE,OAAO,EACf,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,CA0HlF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAumB5D"}
|
package/dist/commands/record.js
CHANGED
|
@@ -7,12 +7,12 @@ 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";
|
|
10
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
11
11
|
/**
|
|
12
12
|
* Process records from stdin (JSON single object or array)
|
|
13
13
|
* Validates, dedups, and appends with file locking
|
|
14
14
|
*/
|
|
15
|
-
export async function processStdinRecords(domain, jsonMode, force, stdinData, cwd) {
|
|
15
|
+
export async function processStdinRecords(domain, jsonMode, force, dryRun, stdinData, cwd) {
|
|
16
16
|
const config = await readConfig(cwd);
|
|
17
17
|
if (!config.domains.includes(domain)) {
|
|
18
18
|
throw new Error(`Domain "${domain}" not found in config. Available domains: ${config.domains.join(", ") || "(none)"}`);
|
|
@@ -55,14 +55,15 @@ export async function processStdinRecords(domain, jsonMode, force, stdinData, cw
|
|
|
55
55
|
if (validRecords.length === 0) {
|
|
56
56
|
return { created: 0, updated: 0, skipped: 0, errors };
|
|
57
57
|
}
|
|
58
|
-
// Process valid records with file locking
|
|
58
|
+
// Process valid records with file locking (skip write in dry-run mode)
|
|
59
59
|
const filePath = getExpertisePath(domain, cwd);
|
|
60
60
|
let created = 0;
|
|
61
61
|
let updated = 0;
|
|
62
62
|
let skipped = 0;
|
|
63
|
-
|
|
63
|
+
if (dryRun) {
|
|
64
|
+
// Dry-run: check for duplicates without writing
|
|
64
65
|
const existing = await readExpertiseFile(filePath);
|
|
65
|
-
|
|
66
|
+
const currentRecords = [...existing];
|
|
66
67
|
for (const record of validRecords) {
|
|
67
68
|
const dup = findDuplicate(currentRecords, record);
|
|
68
69
|
if (dup && !force) {
|
|
@@ -71,26 +72,51 @@ export async function processStdinRecords(domain, jsonMode, force, stdinData, cw
|
|
|
71
72
|
record.type === "reference" ||
|
|
72
73
|
record.type === "guide";
|
|
73
74
|
if (isNamed) {
|
|
74
|
-
// Upsert: replace in place
|
|
75
|
-
currentRecords[dup.index] = record;
|
|
76
75
|
updated++;
|
|
77
76
|
}
|
|
78
77
|
else {
|
|
79
|
-
// Exact match: skip
|
|
80
78
|
skipped++;
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
81
|
else {
|
|
84
|
-
// New record: append
|
|
85
|
-
currentRecords.push(record);
|
|
86
82
|
created++;
|
|
87
83
|
}
|
|
88
84
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Normal mode: write with file locking
|
|
88
|
+
await withFileLock(filePath, async () => {
|
|
89
|
+
const existing = await readExpertiseFile(filePath);
|
|
90
|
+
let currentRecords = [...existing];
|
|
91
|
+
for (const record of validRecords) {
|
|
92
|
+
const dup = findDuplicate(currentRecords, record);
|
|
93
|
+
if (dup && !force) {
|
|
94
|
+
const isNamed = record.type === "pattern" ||
|
|
95
|
+
record.type === "decision" ||
|
|
96
|
+
record.type === "reference" ||
|
|
97
|
+
record.type === "guide";
|
|
98
|
+
if (isNamed) {
|
|
99
|
+
// Upsert: replace in place
|
|
100
|
+
currentRecords[dup.index] = record;
|
|
101
|
+
updated++;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Exact match: skip
|
|
105
|
+
skipped++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// New record: append
|
|
110
|
+
currentRecords.push(record);
|
|
111
|
+
created++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Write all changes at once
|
|
115
|
+
if (created > 0 || updated > 0) {
|
|
116
|
+
await writeExpertiseFile(filePath, currentRecords);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
94
120
|
return { created, updated, skipped, errors };
|
|
95
121
|
}
|
|
96
122
|
export function registerRecordCommand(program) {
|
|
@@ -119,12 +145,116 @@ export function registerRecordCommand(program) {
|
|
|
119
145
|
.option("--supersedes <ids>", "comma-separated record IDs this supersedes")
|
|
120
146
|
.option("--force", "force recording even if duplicate exists")
|
|
121
147
|
.option("--stdin", "read JSON record(s) from stdin (single object or array)")
|
|
148
|
+
.option("--batch <file>", "read JSON record(s) from file (single object or array)")
|
|
149
|
+
.option("--dry-run", "preview what would be recorded without writing")
|
|
150
|
+
.addHelpText("after", `
|
|
151
|
+
Required fields per record type:
|
|
152
|
+
convention [content] or --description
|
|
153
|
+
pattern --name, --description (or [content])
|
|
154
|
+
failure --description, --resolution
|
|
155
|
+
decision --title, --rationale
|
|
156
|
+
reference --name, --description (or [content])
|
|
157
|
+
guide --name, --description (or [content])
|
|
158
|
+
|
|
159
|
+
Batch recording examples:
|
|
160
|
+
mulch record cli --batch records.json
|
|
161
|
+
mulch record cli --batch records.json --dry-run
|
|
162
|
+
echo '[{"type":"convention","content":"test"}]' > batch.json && mulch record cli --batch batch.json
|
|
163
|
+
`)
|
|
122
164
|
.action(async (domain, content, options) => {
|
|
123
165
|
const jsonMode = program.opts().json === true;
|
|
166
|
+
// Handle --batch mode
|
|
167
|
+
if (options.batch) {
|
|
168
|
+
const batchFile = options.batch;
|
|
169
|
+
const dryRun = options.dryRun === true;
|
|
170
|
+
if (!existsSync(batchFile)) {
|
|
171
|
+
if (jsonMode) {
|
|
172
|
+
outputJsonError("record", `Batch file not found: ${batchFile}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.error(chalk.red(`Error: batch file not found: ${batchFile}`));
|
|
176
|
+
}
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const fileContent = readFileSync(batchFile, "utf-8");
|
|
182
|
+
const result = await processStdinRecords(domain, jsonMode, options.force === true, dryRun, fileContent);
|
|
183
|
+
if (result.errors.length > 0) {
|
|
184
|
+
if (jsonMode) {
|
|
185
|
+
outputJsonError("record", `Validation errors: ${result.errors.join("; ")}`);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
console.error(chalk.red("Validation errors:"));
|
|
189
|
+
for (const error of result.errors) {
|
|
190
|
+
console.error(chalk.red(` ${error}`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (jsonMode) {
|
|
195
|
+
outputJson({
|
|
196
|
+
success: result.errors.length === 0 || result.created + result.updated > 0,
|
|
197
|
+
command: "record",
|
|
198
|
+
action: dryRun ? "dry-run" : "batch",
|
|
199
|
+
domain,
|
|
200
|
+
created: result.created,
|
|
201
|
+
updated: result.updated,
|
|
202
|
+
skipped: result.skipped,
|
|
203
|
+
errors: result.errors,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
if (dryRun) {
|
|
208
|
+
const total = result.created + result.updated;
|
|
209
|
+
if (total > 0 || result.skipped > 0) {
|
|
210
|
+
console.log(chalk.green(`✓ Dry-run complete. Would process ${total} record(s) in ${domain}:`));
|
|
211
|
+
if (result.created > 0) {
|
|
212
|
+
console.log(chalk.dim(` Create: ${result.created}`));
|
|
213
|
+
}
|
|
214
|
+
if (result.updated > 0) {
|
|
215
|
+
console.log(chalk.dim(` Update: ${result.updated}`));
|
|
216
|
+
}
|
|
217
|
+
if (result.skipped > 0) {
|
|
218
|
+
console.log(chalk.dim(` Skip: ${result.skipped}`));
|
|
219
|
+
}
|
|
220
|
+
console.log(chalk.dim(" Run without --dry-run to apply changes."));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(chalk.yellow("No records would be processed."));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (result.created > 0) {
|
|
228
|
+
console.log(chalk.green(`✔ Created ${result.created} record(s) in ${domain}`));
|
|
229
|
+
}
|
|
230
|
+
if (result.updated > 0) {
|
|
231
|
+
console.log(chalk.green(`✔ Updated ${result.updated} record(s) in ${domain}`));
|
|
232
|
+
}
|
|
233
|
+
if (result.skipped > 0) {
|
|
234
|
+
console.log(chalk.yellow(`Skipped ${result.skipped} duplicate(s) in ${domain}`));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (result.errors.length > 0 && result.created + result.updated === 0) {
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
if (jsonMode) {
|
|
244
|
+
outputJsonError("record", err instanceof Error ? err.message : String(err));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
248
|
+
}
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
124
253
|
// Handle --stdin mode
|
|
125
254
|
if (options.stdin === true) {
|
|
255
|
+
const dryRun = options.dryRun === true;
|
|
126
256
|
try {
|
|
127
|
-
const result = await processStdinRecords(domain, jsonMode, options.force === true);
|
|
257
|
+
const result = await processStdinRecords(domain, jsonMode, options.force === true, dryRun);
|
|
128
258
|
if (result.errors.length > 0) {
|
|
129
259
|
if (jsonMode) {
|
|
130
260
|
outputJsonError("record", `Validation errors: ${result.errors.join("; ")}`);
|
|
@@ -140,6 +270,7 @@ export function registerRecordCommand(program) {
|
|
|
140
270
|
outputJson({
|
|
141
271
|
success: result.errors.length === 0 || result.created + result.updated > 0,
|
|
142
272
|
command: "record",
|
|
273
|
+
action: dryRun ? "dry-run" : "stdin",
|
|
143
274
|
domain,
|
|
144
275
|
created: result.created,
|
|
145
276
|
updated: result.updated,
|
|
@@ -148,14 +279,35 @@ export function registerRecordCommand(program) {
|
|
|
148
279
|
});
|
|
149
280
|
}
|
|
150
281
|
else {
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
282
|
+
if (dryRun) {
|
|
283
|
+
const total = result.created + result.updated;
|
|
284
|
+
if (total > 0 || result.skipped > 0) {
|
|
285
|
+
console.log(chalk.green(`✓ Dry-run complete. Would process ${total} record(s) in ${domain}:`));
|
|
286
|
+
if (result.created > 0) {
|
|
287
|
+
console.log(chalk.dim(` Create: ${result.created}`));
|
|
288
|
+
}
|
|
289
|
+
if (result.updated > 0) {
|
|
290
|
+
console.log(chalk.dim(` Update: ${result.updated}`));
|
|
291
|
+
}
|
|
292
|
+
if (result.skipped > 0) {
|
|
293
|
+
console.log(chalk.dim(` Skip: ${result.skipped}`));
|
|
294
|
+
}
|
|
295
|
+
console.log(chalk.dim(" Run without --dry-run to apply changes."));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(chalk.yellow("No records would be processed."));
|
|
299
|
+
}
|
|
156
300
|
}
|
|
157
|
-
|
|
158
|
-
|
|
301
|
+
else {
|
|
302
|
+
if (result.created > 0) {
|
|
303
|
+
console.log(chalk.green(`✔ Created ${result.created} record(s) in ${domain}`));
|
|
304
|
+
}
|
|
305
|
+
if (result.updated > 0) {
|
|
306
|
+
console.log(chalk.green(`✔ Updated ${result.updated} record(s) in ${domain}`));
|
|
307
|
+
}
|
|
308
|
+
if (result.skipped > 0) {
|
|
309
|
+
console.log(chalk.yellow(`Skipped ${result.skipped} duplicate(s) in ${domain}`));
|
|
310
|
+
}
|
|
159
311
|
}
|
|
160
312
|
}
|
|
161
313
|
if (result.errors.length > 0 && result.created + result.updated === 0) {
|
|
@@ -411,65 +563,103 @@ export function registerRecordCommand(program) {
|
|
|
411
563
|
return;
|
|
412
564
|
}
|
|
413
565
|
const filePath = getExpertisePath(domain);
|
|
414
|
-
|
|
566
|
+
const dryRun = options.dryRun === true;
|
|
567
|
+
if (dryRun) {
|
|
568
|
+
// Dry-run: check for duplicates without writing
|
|
415
569
|
const existing = await readExpertiseFile(filePath);
|
|
416
570
|
const dup = findDuplicate(existing, record);
|
|
571
|
+
let action = "created";
|
|
417
572
|
if (dup && !options.force) {
|
|
418
573
|
const isNamed = record.type === "pattern" || record.type === "decision" ||
|
|
419
574
|
record.type === "reference" || record.type === "guide";
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
575
|
+
action = isNamed ? "updated" : "skipped";
|
|
576
|
+
}
|
|
577
|
+
if (jsonMode) {
|
|
578
|
+
outputJson({
|
|
579
|
+
success: true,
|
|
580
|
+
command: "record",
|
|
581
|
+
action: "dry-run",
|
|
582
|
+
wouldDo: action,
|
|
583
|
+
domain,
|
|
584
|
+
type: recordType,
|
|
585
|
+
record,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
if (action === "created") {
|
|
590
|
+
console.log(chalk.green(`✓ Dry-run: Would create ${recordType} in ${domain}`));
|
|
591
|
+
}
|
|
592
|
+
else if (action === "updated") {
|
|
593
|
+
console.log(chalk.green(`✓ Dry-run: Would update existing ${recordType} in ${domain}`));
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
console.log(chalk.yellow(`Dry-run: Duplicate ${recordType} already exists in ${domain}. Would skip.`));
|
|
597
|
+
}
|
|
598
|
+
console.log(chalk.dim(" Run without --dry-run to apply changes."));
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// Normal mode: write with file locking
|
|
603
|
+
await withFileLock(filePath, async () => {
|
|
604
|
+
const existing = await readExpertiseFile(filePath);
|
|
605
|
+
const dup = findDuplicate(existing, record);
|
|
606
|
+
if (dup && !options.force) {
|
|
607
|
+
const isNamed = record.type === "pattern" || record.type === "decision" ||
|
|
608
|
+
record.type === "reference" || record.type === "guide";
|
|
609
|
+
if (isNamed) {
|
|
610
|
+
// Upsert: replace in place
|
|
611
|
+
existing[dup.index] = record;
|
|
612
|
+
await writeExpertiseFile(filePath, existing);
|
|
613
|
+
if (jsonMode) {
|
|
614
|
+
outputJson({
|
|
615
|
+
success: true,
|
|
616
|
+
command: "record",
|
|
617
|
+
action: "updated",
|
|
618
|
+
domain,
|
|
619
|
+
type: recordType,
|
|
620
|
+
index: dup.index + 1,
|
|
621
|
+
record,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
console.log(chalk.green(`\u2714 Updated existing ${recordType} in ${domain} (record #${dup.index + 1})`));
|
|
626
|
+
}
|
|
434
627
|
}
|
|
435
628
|
else {
|
|
436
|
-
|
|
629
|
+
// Exact match: skip
|
|
630
|
+
if (jsonMode) {
|
|
631
|
+
outputJson({
|
|
632
|
+
success: true,
|
|
633
|
+
command: "record",
|
|
634
|
+
action: "skipped",
|
|
635
|
+
domain,
|
|
636
|
+
type: recordType,
|
|
637
|
+
index: dup.index + 1,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
console.log(chalk.yellow(`Duplicate ${recordType} already exists in ${domain} (record #${dup.index + 1}). Use --force to add anyway.`));
|
|
642
|
+
}
|
|
437
643
|
}
|
|
438
644
|
}
|
|
439
645
|
else {
|
|
440
|
-
|
|
646
|
+
await appendRecord(filePath, record);
|
|
441
647
|
if (jsonMode) {
|
|
442
648
|
outputJson({
|
|
443
649
|
success: true,
|
|
444
650
|
command: "record",
|
|
445
|
-
action: "
|
|
651
|
+
action: "created",
|
|
446
652
|
domain,
|
|
447
653
|
type: recordType,
|
|
448
|
-
|
|
654
|
+
record,
|
|
449
655
|
});
|
|
450
656
|
}
|
|
451
657
|
else {
|
|
452
|
-
console.log(chalk.
|
|
658
|
+
console.log(chalk.green(`\u2714 Recorded ${recordType} in ${domain}`));
|
|
453
659
|
}
|
|
454
660
|
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
await appendRecord(filePath, record);
|
|
458
|
-
if (jsonMode) {
|
|
459
|
-
outputJson({
|
|
460
|
-
success: true,
|
|
461
|
-
command: "record",
|
|
462
|
-
action: "created",
|
|
463
|
-
domain,
|
|
464
|
-
type: recordType,
|
|
465
|
-
record,
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
console.log(chalk.green(`\u2714 Recorded ${recordType} in ${domain}`));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
473
663
|
});
|
|
474
664
|
}
|
|
475
665
|
//# sourceMappingURL=record.js.map
|