@kiwidata/grimoire 0.1.1
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/.claude-plugin/plugin.json +8 -0
- package/AGENTS.md +217 -0
- package/README.md +748 -0
- package/bin/grimoire.js +2 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +42 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/archive.d.ts +3 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +22 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/branch-check.d.ts +3 -0
- package/dist/commands/branch-check.d.ts.map +1 -0
- package/dist/commands/branch-check.js +16 -0
- package/dist/commands/branch-check.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +22 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/ci.d.ts +3 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +18 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/diff.d.ts +3 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +10 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/docs.d.ts +3 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +11 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/health.d.ts +3 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +13 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +21 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +22 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/log.d.ts +3 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +15 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/map.d.ts +3 -0
- package/dist/commands/map.d.ts.map +1 -0
- package/dist/commands/map.js +17 -0
- package/dist/commands/map.js.map +1 -0
- package/dist/commands/pr.d.ts +3 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +17 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +12 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/test-quality.d.ts +3 -0
- package/dist/commands/test-quality.d.ts.map +1 -0
- package/dist/commands/test-quality.js +37 -0
- package/dist/commands/test-quality.js.map +1 -0
- package/dist/commands/trace.d.ts +3 -0
- package/dist/commands/trace.d.ts.map +1 -0
- package/dist/commands/trace.js +12 -0
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +22 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +17 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/core/archive.d.ts +9 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +92 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/branch-check.d.ts +27 -0
- package/dist/core/branch-check.d.ts.map +1 -0
- package/dist/core/branch-check.js +205 -0
- package/dist/core/branch-check.js.map +1 -0
- package/dist/core/check.d.ts +24 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +372 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/ci.d.ts +24 -0
- package/dist/core/ci.d.ts.map +1 -0
- package/dist/core/ci.js +162 -0
- package/dist/core/ci.js.map +1 -0
- package/dist/core/detect.d.ts +10 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +368 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/diff.d.ts +29 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +197 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/doc-style.d.ts +16 -0
- package/dist/core/doc-style.d.ts.map +1 -0
- package/dist/core/doc-style.js +192 -0
- package/dist/core/doc-style.js.map +1 -0
- package/dist/core/docs.d.ts +6 -0
- package/dist/core/docs.d.ts.map +1 -0
- package/dist/core/docs.js +478 -0
- package/dist/core/docs.js.map +1 -0
- package/dist/core/health.d.ts +7 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +489 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +168 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/init.d.ts +9 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +563 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/list.d.ts +4 -0
- package/dist/core/list.d.ts.map +1 -0
- package/dist/core/list.js +170 -0
- package/dist/core/list.js.map +1 -0
- package/dist/core/log.d.ts +8 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +150 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/map.d.ts +9 -0
- package/dist/core/map.d.ts.map +1 -0
- package/dist/core/map.js +302 -0
- package/dist/core/map.js.map +1 -0
- package/dist/core/pr.d.ts +9 -0
- package/dist/core/pr.d.ts.map +1 -0
- package/dist/core/pr.js +273 -0
- package/dist/core/pr.js.map +1 -0
- package/dist/core/shared-setup.d.ts +52 -0
- package/dist/core/shared-setup.d.ts.map +1 -0
- package/dist/core/shared-setup.js +221 -0
- package/dist/core/shared-setup.js.map +1 -0
- package/dist/core/status.d.ts +6 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +114 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/test-quality.d.ts +33 -0
- package/dist/core/test-quality.d.ts.map +1 -0
- package/dist/core/test-quality.js +378 -0
- package/dist/core/test-quality.js.map +1 -0
- package/dist/core/trace.d.ts +6 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/trace.js +211 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/core/update.d.ts +10 -0
- package/dist/core/update.d.ts.map +1 -0
- package/dist/core/update.js +149 -0
- package/dist/core/update.js.map +1 -0
- package/dist/core/validate.d.ts +20 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +275 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/config.d.ts +61 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +172 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/fs.d.ts +17 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +38 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +35 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/spawn.d.ts +5 -0
- package/dist/utils/spawn.d.ts.map +1 -0
- package/dist/utils/spawn.js +34 -0
- package/dist/utils/spawn.js.map +1 -0
- package/package.json +68 -0
- package/skills/grimoire-apply/SKILL.md +274 -0
- package/skills/grimoire-audit/SKILL.md +129 -0
- package/skills/grimoire-branch-guard/SKILL.md +111 -0
- package/skills/grimoire-bug/SKILL.md +160 -0
- package/skills/grimoire-bug-explore/SKILL.md +242 -0
- package/skills/grimoire-bug-report/SKILL.md +237 -0
- package/skills/grimoire-bug-session/SKILL.md +222 -0
- package/skills/grimoire-bug-triage/SKILL.md +274 -0
- package/skills/grimoire-commit/SKILL.md +150 -0
- package/skills/grimoire-discover/SKILL.md +297 -0
- package/skills/grimoire-draft/SKILL.md +202 -0
- package/skills/grimoire-plan/SKILL.md +329 -0
- package/skills/grimoire-pr/SKILL.md +134 -0
- package/skills/grimoire-pr-review/SKILL.md +240 -0
- package/skills/grimoire-refactor/SKILL.md +251 -0
- package/skills/grimoire-remove/SKILL.md +112 -0
- package/skills/grimoire-review/SKILL.md +247 -0
- package/skills/grimoire-verify/SKILL.md +223 -0
- package/skills/references/bug-classification.md +154 -0
- package/skills/references/build-vs-buy.md +77 -0
- package/skills/references/elicitation-personas.md +118 -0
- package/skills/references/refactor-register-format.md +88 -0
- package/skills/references/refactor-scan-categories.md +102 -0
- package/skills/references/schema-format.md +68 -0
- package/skills/references/security-compliance.md +110 -0
- package/skills/references/testing-contracts.md +93 -0
- package/templates/context.yml +110 -0
- package/templates/debt-exceptions.yml +61 -0
- package/templates/decision.md +50 -0
- package/templates/dupignore +93 -0
- package/templates/example.feature +24 -0
- package/templates/manifest.md +29 -0
- package/templates/mapignore +58 -0
- package/templates/mapkeys +65 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
import { findProjectRoot } from "../utils/paths.js";
|
|
6
|
+
import { fileExists } from "../utils/fs.js";
|
|
7
|
+
export async function listChanges(json) {
|
|
8
|
+
const root = await findProjectRoot();
|
|
9
|
+
const changesDir = join(root, ".grimoire", "changes");
|
|
10
|
+
try {
|
|
11
|
+
const entries = await readdir(changesDir, { withFileTypes: true });
|
|
12
|
+
const changes = entries.filter((e) => e.isDirectory());
|
|
13
|
+
if (changes.length === 0) {
|
|
14
|
+
if (json) {
|
|
15
|
+
console.log(JSON.stringify([]));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.log("No active changes.");
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const change of changes) {
|
|
24
|
+
const changePath = join(changesDir, change.name);
|
|
25
|
+
const hasManifest = await fileExists(join(changePath, "manifest.md"));
|
|
26
|
+
const hasTasks = await fileExists(join(changePath, "tasks.md"));
|
|
27
|
+
const glob = (await import("fast-glob")).default;
|
|
28
|
+
const featureFiles = await glob("features/**/*.feature", {
|
|
29
|
+
cwd: changePath,
|
|
30
|
+
});
|
|
31
|
+
const hasFeatures = featureFiles.length > 0;
|
|
32
|
+
const hasDecisions = await dirHasFiles(join(changePath, "decisions"), ".md");
|
|
33
|
+
// Parse manifest frontmatter
|
|
34
|
+
let status = "draft";
|
|
35
|
+
let branch = null;
|
|
36
|
+
if (hasManifest) {
|
|
37
|
+
const manifestContent = await readFile(join(changePath, "manifest.md"), "utf-8");
|
|
38
|
+
const { data: fm } = matter(manifestContent);
|
|
39
|
+
if (fm.status)
|
|
40
|
+
status = fm.status;
|
|
41
|
+
if (fm.branch)
|
|
42
|
+
branch = fm.branch;
|
|
43
|
+
}
|
|
44
|
+
let stage = "draft";
|
|
45
|
+
if (hasTasks)
|
|
46
|
+
stage = "planned";
|
|
47
|
+
results.push({
|
|
48
|
+
id: change.name,
|
|
49
|
+
status,
|
|
50
|
+
branch,
|
|
51
|
+
stage,
|
|
52
|
+
hasManifest,
|
|
53
|
+
hasTasks,
|
|
54
|
+
hasFeatures,
|
|
55
|
+
hasDecisions,
|
|
56
|
+
featureFiles,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Detect conflicts: multiple changes touching the same feature file
|
|
60
|
+
const conflicts = detectConflicts(results);
|
|
61
|
+
if (json) {
|
|
62
|
+
console.log(JSON.stringify({ changes: results, conflicts }, null, 2));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(chalk.bold("Active changes:\n"));
|
|
66
|
+
for (const r of results) {
|
|
67
|
+
const artifacts = [
|
|
68
|
+
r.hasManifest ? "manifest" : null,
|
|
69
|
+
r.hasFeatures ? "features" : null,
|
|
70
|
+
r.hasDecisions ? "decisions" : null,
|
|
71
|
+
r.hasTasks ? "tasks" : null,
|
|
72
|
+
]
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.join(", ");
|
|
75
|
+
const branchInfo = r.branch
|
|
76
|
+
? ` ${chalk.dim(`→ ${r.branch}`)}`
|
|
77
|
+
: "";
|
|
78
|
+
console.log(` ${chalk.cyan(r.id)} ${chalk.dim(`[${r.status}]`)} — ${artifacts}${branchInfo}`);
|
|
79
|
+
}
|
|
80
|
+
if (conflicts.length > 0) {
|
|
81
|
+
console.log(chalk.bold.yellow("\nConflicts detected:\n"));
|
|
82
|
+
for (const c of conflicts) {
|
|
83
|
+
console.log(` ${chalk.yellow("!")} ${chalk.bold(c.file)} is touched by: ${c.changes.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk.dim("\n These changes modify the same feature file. Coordinate before applying."));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
if (json) {
|
|
91
|
+
console.log(JSON.stringify({ changes: [], conflicts: [] }));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log("No .grimoire/changes/ directory. Run grimoire init first.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function detectConflicts(changes) {
|
|
99
|
+
const fileToChanges = new Map();
|
|
100
|
+
for (const change of changes) {
|
|
101
|
+
for (const file of change.featureFiles) {
|
|
102
|
+
const existing = fileToChanges.get(file) || [];
|
|
103
|
+
existing.push(change.id);
|
|
104
|
+
fileToChanges.set(file, existing);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const conflicts = [];
|
|
108
|
+
for (const [file, changeIds] of fileToChanges) {
|
|
109
|
+
if (changeIds.length > 1) {
|
|
110
|
+
conflicts.push({ file, changes: changeIds });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return conflicts;
|
|
114
|
+
}
|
|
115
|
+
export async function listFeatures(json) {
|
|
116
|
+
const root = await findProjectRoot();
|
|
117
|
+
const glob = (await import("fast-glob")).default;
|
|
118
|
+
const features = await glob("features/**/*.feature", {
|
|
119
|
+
cwd: root,
|
|
120
|
+
absolute: false,
|
|
121
|
+
});
|
|
122
|
+
if (json) {
|
|
123
|
+
console.log(JSON.stringify(features));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (features.length === 0) {
|
|
127
|
+
console.log("No feature files found.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.log(chalk.bold("Feature files:\n"));
|
|
131
|
+
for (const f of features) {
|
|
132
|
+
console.log(` ${f}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export async function listDecisions(json) {
|
|
137
|
+
const root = await findProjectRoot();
|
|
138
|
+
const glob = (await import("fast-glob")).default;
|
|
139
|
+
const decisions = await glob(".grimoire/decisions/[0-9]*.md", {
|
|
140
|
+
cwd: root,
|
|
141
|
+
absolute: false,
|
|
142
|
+
});
|
|
143
|
+
if (json) {
|
|
144
|
+
console.log(JSON.stringify(decisions));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (decisions.length === 0) {
|
|
148
|
+
console.log("No decision records found.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
console.log(chalk.bold("Decision records:\n"));
|
|
152
|
+
for (const d of decisions) {
|
|
153
|
+
const content = await readFile(join(root, d), "utf-8");
|
|
154
|
+
const titleMatch = content.match(/^# (.+)$/m);
|
|
155
|
+
const title = titleMatch ? titleMatch[1] : d;
|
|
156
|
+
console.log(` ${chalk.dim(d.replace(".grimoire/decisions/", ""))} ${title}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// fileExists imported from utils/fs.js
|
|
161
|
+
async function dirHasFiles(dir, ext) {
|
|
162
|
+
try {
|
|
163
|
+
const entries = await readdir(dir, { recursive: true });
|
|
164
|
+
return entries.some((e) => e.endsWith(ext));
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/core/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/core/log.ts"],"names":[],"mappings":"AAMA,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC;CACf;AAYD,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EpE"}
|
package/dist/core/log.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { simpleGit } from "simple-git";
|
|
5
|
+
import { findProjectRoot } from "../utils/paths.js";
|
|
6
|
+
export async function generateLog(options) {
|
|
7
|
+
const root = await findProjectRoot();
|
|
8
|
+
const archiveDir = join(root, ".grimoire", "archive");
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = await readArchiveEntries(archiveDir);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error("No archive found. No changes have been archived yet.");
|
|
15
|
+
}
|
|
16
|
+
if (entries.length === 0) {
|
|
17
|
+
console.log(chalk.dim("No archived changes found."));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Filter by date range if tags/dates provided
|
|
21
|
+
if (options.from || options.to) {
|
|
22
|
+
const fromDate = options.from
|
|
23
|
+
? await resolveDate(root, options.from)
|
|
24
|
+
: "";
|
|
25
|
+
const toDate = options.to
|
|
26
|
+
? await resolveDate(root, options.to)
|
|
27
|
+
: "9999-99-99";
|
|
28
|
+
entries = entries.filter((e) => e.date >= fromDate && e.date <= toDate);
|
|
29
|
+
}
|
|
30
|
+
// Sort newest first
|
|
31
|
+
entries.sort((a, b) => b.date.localeCompare(a.date));
|
|
32
|
+
if (options.json) {
|
|
33
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Pretty output
|
|
37
|
+
console.log(chalk.bold("Grimoire Change Log\n"));
|
|
38
|
+
let currentMonth = "";
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const month = entry.date.slice(0, 7); // YYYY-MM
|
|
41
|
+
if (month !== currentMonth) {
|
|
42
|
+
currentMonth = month;
|
|
43
|
+
console.log(chalk.bold.underline(`\n${formatMonth(month)}\n`));
|
|
44
|
+
}
|
|
45
|
+
console.log(` ${chalk.dim(entry.date)} ${chalk.cyan(entry.changeId)}`);
|
|
46
|
+
console.log(` ${entry.summary}`);
|
|
47
|
+
if (entry.features.length > 0) {
|
|
48
|
+
console.log(` ${chalk.dim("Features:")} ${entry.features.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
if (entry.decisions.length > 0) {
|
|
51
|
+
console.log(` ${chalk.dim("Decisions:")} ${entry.decisions.join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
if (entry.scenarios.length > 0) {
|
|
54
|
+
const display = entry.scenarios.length <= 3
|
|
55
|
+
? entry.scenarios.join(", ")
|
|
56
|
+
: `${entry.scenarios.slice(0, 3).join(", ")} +${entry.scenarios.length - 3} more`;
|
|
57
|
+
console.log(` ${chalk.dim("Scenarios:")} ${display}`);
|
|
58
|
+
}
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.dim(`${entries.length} change(s) total`));
|
|
62
|
+
}
|
|
63
|
+
async function readArchiveEntries(archiveDir) {
|
|
64
|
+
const dirs = await readdir(archiveDir, { withFileTypes: true });
|
|
65
|
+
const entries = [];
|
|
66
|
+
for (const dir of dirs) {
|
|
67
|
+
if (!dir.isDirectory())
|
|
68
|
+
continue;
|
|
69
|
+
// Directory name format: YYYY-MM-DD-<change-id>
|
|
70
|
+
const match = dir.name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
|
|
71
|
+
if (!match)
|
|
72
|
+
continue;
|
|
73
|
+
const [, date, changeId] = match;
|
|
74
|
+
const manifestPath = join(archiveDir, dir.name, "manifest.md");
|
|
75
|
+
let manifest;
|
|
76
|
+
try {
|
|
77
|
+
manifest = await readFile(manifestPath, "utf-8");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
entries.push({
|
|
83
|
+
date,
|
|
84
|
+
changeId,
|
|
85
|
+
...parseManifest(manifest),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return entries;
|
|
89
|
+
}
|
|
90
|
+
function parseManifest(content) {
|
|
91
|
+
// Extract title: # Change: <summary>
|
|
92
|
+
const titleMatch = content.match(/^#\s+Change:\s*(.+)$/m);
|
|
93
|
+
const summary = titleMatch ? titleMatch[1].trim() : "(no summary)";
|
|
94
|
+
// Extract why section
|
|
95
|
+
const whyMatch = content.match(/^##\s+Why\s*\n([\s\S]*?)(?=^##|\Z)/m);
|
|
96
|
+
const why = whyMatch ? whyMatch[1].trim() : "";
|
|
97
|
+
// Extract feature changes
|
|
98
|
+
const features = [];
|
|
99
|
+
const featurePattern = /\*\*(?:ADDED|MODIFIED|REMOVED)\*\*\s+`([^`]+\.feature)`/g;
|
|
100
|
+
let m;
|
|
101
|
+
while ((m = featurePattern.exec(content)) !== null) {
|
|
102
|
+
features.push(m[1]);
|
|
103
|
+
}
|
|
104
|
+
// Extract decisions
|
|
105
|
+
const decisions = [];
|
|
106
|
+
const decisionPattern = /\*\*(?:ADDED|MODIFIED|SUPERSEDED)\*\*\s+`(\d{4}-[^`]+\.md)`/g;
|
|
107
|
+
while ((m = decisionPattern.exec(content)) !== null) {
|
|
108
|
+
decisions.push(m[1]);
|
|
109
|
+
}
|
|
110
|
+
// Extract scenarios
|
|
111
|
+
const scenarios = [];
|
|
112
|
+
const scenarioPattern = /"([^"]+)"/g;
|
|
113
|
+
const scenarioSection = content.match(/^##\s+Scenarios\s+(?:Added|Modified)\s*\n([\s\S]*?)(?=^##|\Z)/gm);
|
|
114
|
+
if (scenarioSection) {
|
|
115
|
+
for (const section of scenarioSection) {
|
|
116
|
+
while ((m = scenarioPattern.exec(section)) !== null) {
|
|
117
|
+
scenarios.push(m[1]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { summary, why, features, decisions, scenarios };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a git tag or date string to an ISO date.
|
|
125
|
+
* If it looks like a date already, return it. Otherwise try git tag.
|
|
126
|
+
*/
|
|
127
|
+
async function resolveDate(root, ref) {
|
|
128
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(ref)) {
|
|
129
|
+
return ref;
|
|
130
|
+
}
|
|
131
|
+
// Try as a git tag
|
|
132
|
+
try {
|
|
133
|
+
const git = simpleGit(root);
|
|
134
|
+
const stdout = await git.raw(["log", "-1", "--format=%aI", ref]);
|
|
135
|
+
return stdout.trim().split("T")[0];
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
console.error(chalk.yellow(`Warning: Could not resolve "${ref}" as a git tag or date. Using as-is.`));
|
|
139
|
+
return ref;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function formatMonth(yyyyMm) {
|
|
143
|
+
const [year, month] = yyyyMm.split("-");
|
|
144
|
+
const months = [
|
|
145
|
+
"January", "February", "March", "April", "May", "June",
|
|
146
|
+
"July", "August", "September", "October", "November", "December",
|
|
147
|
+
];
|
|
148
|
+
return `${months[parseInt(month, 10) - 1]} ${year}`;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/core/log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/core/map.ts"],"names":[],"mappings":"AAaA,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAiGD,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0KpE"}
|
package/dist/core/map.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join, relative, extname, basename, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { findProjectRoot } from "../utils/paths.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const PACKAGE_ROOT = join(__dirname, "..", "..");
|
|
11
|
+
/**
|
|
12
|
+
* Parse a mapignore file into a Set of patterns.
|
|
13
|
+
* Blank lines and lines starting with # are skipped.
|
|
14
|
+
*/
|
|
15
|
+
function parseIgnoreFile(content) {
|
|
16
|
+
const patterns = new Set();
|
|
17
|
+
for (const line of content.split("\n")) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
20
|
+
patterns.add(trimmed);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return patterns;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a mapkeys file into a Record<filename, type>.
|
|
27
|
+
* Format: filename = type
|
|
28
|
+
* Blank lines and lines starting with # are skipped.
|
|
29
|
+
*/
|
|
30
|
+
function parseKeyFilesConfig(content) {
|
|
31
|
+
const keys = {};
|
|
32
|
+
for (const line of content.split("\n")) {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
35
|
+
continue;
|
|
36
|
+
const eqIndex = trimmed.indexOf("=");
|
|
37
|
+
if (eqIndex === -1)
|
|
38
|
+
continue;
|
|
39
|
+
const filename = trimmed.slice(0, eqIndex).trim();
|
|
40
|
+
const type = trimmed.slice(eqIndex + 1).trim();
|
|
41
|
+
if (filename && type) {
|
|
42
|
+
keys[filename] = type;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return keys;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load config from project-level file, falling back to bundled template.
|
|
49
|
+
*/
|
|
50
|
+
async function loadConfigFile(root, filename) {
|
|
51
|
+
// Project-level override: .grimoire/<filename>
|
|
52
|
+
const projectPath = join(root, ".grimoire", filename);
|
|
53
|
+
try {
|
|
54
|
+
return await readFile(projectPath, "utf-8");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fall back to bundled template
|
|
58
|
+
const templatePath = join(PACKAGE_ROOT, "templates", filename);
|
|
59
|
+
return await readFile(templatePath, "utf-8");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function generateMap(options) {
|
|
63
|
+
const root = await findProjectRoot();
|
|
64
|
+
const docsDir = join(root, ".grimoire", "docs");
|
|
65
|
+
// Load config files
|
|
66
|
+
// mapignore → structure scan only (dir-name match)
|
|
67
|
+
// dupignore → jscpd only (glob match)
|
|
68
|
+
const ignoreContent = await loadConfigFile(root, "mapignore");
|
|
69
|
+
const keysContent = await loadConfigFile(root, "mapkeys");
|
|
70
|
+
const dupIgnoreContent = await loadConfigFile(root, "dupignore");
|
|
71
|
+
const ignorePatterns = parseIgnoreFile(ignoreContent);
|
|
72
|
+
const keyFilePatterns = parseKeyFilesConfig(keysContent);
|
|
73
|
+
const dupIgnoreGlobs = parseIgnoreFile(dupIgnoreContent);
|
|
74
|
+
// Scan the directory tree
|
|
75
|
+
const directories = [];
|
|
76
|
+
const keyFiles = [];
|
|
77
|
+
await scanDirectory(root, root, 0, options.maxDepth, directories, keyFiles, ignorePatterns, keyFilePatterns);
|
|
78
|
+
// Load existing index if refreshing
|
|
79
|
+
let existingAreas = [];
|
|
80
|
+
if (options.refresh) {
|
|
81
|
+
existingAreas = await loadExistingAreas(docsDir);
|
|
82
|
+
}
|
|
83
|
+
// Determine what's undocumented and what's been removed
|
|
84
|
+
const scannedDirs = new Set(directories.map((d) => d.path));
|
|
85
|
+
const undocumented = directories
|
|
86
|
+
.filter((d) => !existingAreas.includes(d.path))
|
|
87
|
+
.filter((d) => d.fileCount > 0)
|
|
88
|
+
.map((d) => d.path);
|
|
89
|
+
const removed = existingAreas.filter((a) => !scannedDirs.has(a));
|
|
90
|
+
// Run duplicate detection if requested
|
|
91
|
+
let duplicates = null;
|
|
92
|
+
if (options.duplicates) {
|
|
93
|
+
duplicates = await runJscpd(root, dupIgnoreGlobs);
|
|
94
|
+
}
|
|
95
|
+
const snapshot = {
|
|
96
|
+
generatedAt: new Date().toISOString(),
|
|
97
|
+
projectRoot: ".",
|
|
98
|
+
directories,
|
|
99
|
+
keyFiles,
|
|
100
|
+
undocumented,
|
|
101
|
+
removed,
|
|
102
|
+
duplicates,
|
|
103
|
+
};
|
|
104
|
+
if (options.json) {
|
|
105
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Pretty print
|
|
109
|
+
console.log(chalk.bold("\nProject Map\n"));
|
|
110
|
+
// Directory tree
|
|
111
|
+
console.log(chalk.bold("Structure:"));
|
|
112
|
+
for (const dir of directories) {
|
|
113
|
+
const depth = dir.path === "." ? 0 : dir.path.split("/").length;
|
|
114
|
+
const padding = " ".repeat(depth);
|
|
115
|
+
const extSummary = Object.entries(dir.extensions)
|
|
116
|
+
.sort((a, b) => b[1] - a[1])
|
|
117
|
+
.slice(0, 3)
|
|
118
|
+
.map(([ext, count]) => `${count} ${ext}`)
|
|
119
|
+
.join(", ");
|
|
120
|
+
const keyFileNote = dir.keyFiles.length > 0
|
|
121
|
+
? chalk.dim(` [${dir.keyFiles.join(", ")}]`)
|
|
122
|
+
: "";
|
|
123
|
+
console.log(`${padding}${chalk.cyan(dir.path + "/")} ${chalk.dim(extSummary)}${keyFileNote}`);
|
|
124
|
+
}
|
|
125
|
+
// Key files
|
|
126
|
+
if (keyFiles.length > 0) {
|
|
127
|
+
console.log(chalk.bold("\nKey Files:"));
|
|
128
|
+
for (const kf of keyFiles) {
|
|
129
|
+
console.log(` ${kf.path} ${chalk.dim(`(${kf.type})`)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Duplicate report
|
|
133
|
+
if (duplicates) {
|
|
134
|
+
if (duplicates.clones.length > 0) {
|
|
135
|
+
console.log(chalk.bold.yellow(`\nDuplicates: ${duplicates.clones.length} clone(s), ${duplicates.totalDuplicatedLines} duplicated lines (${duplicates.percentDuplicated.toFixed(1)}%)\n`));
|
|
136
|
+
for (const clone of duplicates.clones.slice(0, 10)) {
|
|
137
|
+
console.log(` ${chalk.dim(clone.firstFile)}:${clone.firstStartLine}-${clone.firstEndLine}`);
|
|
138
|
+
console.log(` ${chalk.dim(clone.secondFile)}:${clone.secondStartLine}-${clone.secondEndLine}`);
|
|
139
|
+
console.log(` ${chalk.dim(`${clone.lines} lines, ${clone.tokens} tokens`)}\n`);
|
|
140
|
+
}
|
|
141
|
+
if (duplicates.clones.length > 10) {
|
|
142
|
+
console.log(chalk.dim(` ... and ${duplicates.clones.length - 10} more (see .snapshot.json for full list)`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(chalk.green("\nNo duplicates detected."));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Diff against existing docs
|
|
150
|
+
if (options.refresh) {
|
|
151
|
+
if (undocumented.length > 0) {
|
|
152
|
+
console.log(chalk.bold.yellow("\nUndocumented areas:"));
|
|
153
|
+
for (const u of undocumented) {
|
|
154
|
+
console.log(` ${chalk.yellow("+")} ${u}/`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (removed.length > 0) {
|
|
158
|
+
console.log(chalk.bold.red("\nRemoved (docs may be stale):"));
|
|
159
|
+
for (const r of removed) {
|
|
160
|
+
console.log(` ${chalk.red("-")} ${r}/`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (undocumented.length === 0 && removed.length === 0) {
|
|
164
|
+
console.log(chalk.green("\nAll areas are documented. No changes detected."));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(chalk.dim(`\n${directories.length} directories, ${keyFiles.length} key files found.`));
|
|
169
|
+
console.log(chalk.dim("Run /grimoire:discover to generate area docs from this snapshot."));
|
|
170
|
+
}
|
|
171
|
+
// Write snapshot for the skill to consume
|
|
172
|
+
await mkdir(docsDir, { recursive: true });
|
|
173
|
+
await writeFile(join(docsDir, ".snapshot.json"), JSON.stringify(snapshot, null, 2));
|
|
174
|
+
console.log(chalk.dim(`\nSnapshot saved to .grimoire/docs/.snapshot.json`));
|
|
175
|
+
}
|
|
176
|
+
async function scanDirectory(fullPath, root, depth, maxDepth, directories, keyFiles, ignorePatterns, keyFilePatterns) {
|
|
177
|
+
if (depth > maxDepth)
|
|
178
|
+
return;
|
|
179
|
+
const relPath = relative(root, fullPath) || ".";
|
|
180
|
+
const dirName = basename(fullPath);
|
|
181
|
+
// Skip ignored directories (by name match)
|
|
182
|
+
if (depth > 0 && ignorePatterns.has(dirName))
|
|
183
|
+
return;
|
|
184
|
+
// Skip hidden dirs except .grimoire
|
|
185
|
+
if (depth > 0 && dirName.startsWith(".") && dirName !== ".grimoire")
|
|
186
|
+
return;
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = await readdir(fullPath, { withFileTypes: true });
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const files = entries.filter((e) => e.isFile());
|
|
195
|
+
const subdirs = entries
|
|
196
|
+
.filter((e) => e.isDirectory())
|
|
197
|
+
.filter((e) => !ignorePatterns.has(e.name))
|
|
198
|
+
.filter((e) => !e.name.startsWith(".") || e.name === ".grimoire");
|
|
199
|
+
// Count extensions and detect key files
|
|
200
|
+
const extensions = {};
|
|
201
|
+
const dirKeyFiles = [];
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
const ext = extname(file.name) || file.name;
|
|
204
|
+
extensions[ext] = (extensions[ext] || 0) + 1;
|
|
205
|
+
if (keyFilePatterns[file.name]) {
|
|
206
|
+
const kfPath = relPath === "." ? file.name : `${relPath}/${file.name}`;
|
|
207
|
+
keyFiles.push({
|
|
208
|
+
path: kfPath,
|
|
209
|
+
type: keyFilePatterns[file.name],
|
|
210
|
+
});
|
|
211
|
+
dirKeyFiles.push(file.name);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Include directories that have files or are shallow enough to show structure
|
|
215
|
+
if (files.length > 0 || depth <= 1) {
|
|
216
|
+
directories.push({
|
|
217
|
+
path: relPath === "." ? "." : relPath,
|
|
218
|
+
fileCount: files.length,
|
|
219
|
+
extensions,
|
|
220
|
+
keyFiles: dirKeyFiles,
|
|
221
|
+
subdirs: subdirs.map((s) => s.name),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
for (const subdir of subdirs) {
|
|
225
|
+
await scanDirectory(join(fullPath, subdir.name), root, depth + 1, maxDepth, directories, keyFiles, ignorePatterns, keyFilePatterns);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function runJscpd(root, dupIgnoreGlobs) {
|
|
229
|
+
// Check if jscpd is available
|
|
230
|
+
try {
|
|
231
|
+
await execFileAsync("npx", ["jscpd", "--version"], { cwd: root });
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
console.log(chalk.yellow("\njscpd not found. Install with: npm install -g jscpd"));
|
|
235
|
+
console.log(chalk.yellow("Skipping duplicate detection.\n"));
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
console.log(chalk.dim("\nRunning jscpd duplicate detection..."));
|
|
239
|
+
try {
|
|
240
|
+
const ignoreArg = [...dupIgnoreGlobs].join(",");
|
|
241
|
+
const args = [
|
|
242
|
+
"jscpd",
|
|
243
|
+
root,
|
|
244
|
+
"--reporters", "json",
|
|
245
|
+
"--output", join(root, ".grimoire", "docs"),
|
|
246
|
+
"--silent",
|
|
247
|
+
];
|
|
248
|
+
if (ignoreArg) {
|
|
249
|
+
args.push("--ignore", ignoreArg);
|
|
250
|
+
}
|
|
251
|
+
await execFileAsync("npx", args, {
|
|
252
|
+
cwd: root,
|
|
253
|
+
timeout: 60_000,
|
|
254
|
+
});
|
|
255
|
+
// Read the jscpd JSON report
|
|
256
|
+
const reportPath = join(root, ".grimoire", "docs", "jscpd-report.json");
|
|
257
|
+
const reportContent = await readFile(reportPath, "utf-8");
|
|
258
|
+
const report = JSON.parse(reportContent);
|
|
259
|
+
const clones = (report.duplicates || []).map((d) => {
|
|
260
|
+
const first = d.firstFile;
|
|
261
|
+
const second = d.secondFile;
|
|
262
|
+
return {
|
|
263
|
+
firstFile: relative(root, first.name),
|
|
264
|
+
firstStartLine: first.startLoc?.line ?? 0,
|
|
265
|
+
firstEndLine: first.endLoc?.line ?? 0,
|
|
266
|
+
secondFile: relative(root, second.name),
|
|
267
|
+
secondStartLine: second.startLoc?.line ?? 0,
|
|
268
|
+
secondEndLine: second.endLoc?.line ?? 0,
|
|
269
|
+
lines: d.lines || 0,
|
|
270
|
+
tokens: d.tokens || 0,
|
|
271
|
+
fragment: (d.fragment || "").slice(0, 200),
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
const stats = report.statistics;
|
|
275
|
+
const totalLines = stats?.total?.lines || 1;
|
|
276
|
+
const dupLines = stats?.total?.duplicatedLines || 0;
|
|
277
|
+
return {
|
|
278
|
+
clones,
|
|
279
|
+
totalDuplicatedLines: dupLines,
|
|
280
|
+
percentDuplicated: (dupLines / totalLines) * 100,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
console.log(chalk.yellow(`\njscpd failed: ${err instanceof Error ? err.message : "unknown error"}`));
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function loadExistingAreas(docsDir) {
|
|
289
|
+
try {
|
|
290
|
+
const indexContent = await readFile(join(docsDir, "index.yml"), "utf-8");
|
|
291
|
+
const areas = [];
|
|
292
|
+
const dirMatches = indexContent.matchAll(/directory:\s*(.+)/g);
|
|
293
|
+
for (const match of dirMatches) {
|
|
294
|
+
areas.push(match[1].trim());
|
|
295
|
+
}
|
|
296
|
+
return areas;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=map.js.map
|