@kiwidata/grimoire 0.1.4 → 0.1.6

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.
Files changed (112) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/AGENTS.md +21 -25
  3. package/LICENSE +36 -0
  4. package/README.md +86 -61
  5. package/dist/cli/index.js +2 -43
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/program.d.ts +4 -0
  8. package/dist/cli/program.d.ts.map +1 -0
  9. package/dist/cli/program.js +45 -0
  10. package/dist/cli/program.js.map +1 -0
  11. package/dist/commands/configure.d.ts.map +1 -1
  12. package/dist/commands/configure.js +2 -1
  13. package/dist/commands/configure.js.map +1 -1
  14. package/dist/core/check.d.ts.map +1 -1
  15. package/dist/core/check.js +47 -11
  16. package/dist/core/check.js.map +1 -1
  17. package/dist/core/ci.d.ts.map +1 -1
  18. package/dist/core/ci.js +2 -2
  19. package/dist/core/ci.js.map +1 -1
  20. package/dist/core/doc-style.d.ts.map +1 -1
  21. package/dist/core/doc-style.js +76 -0
  22. package/dist/core/doc-style.js.map +1 -1
  23. package/dist/core/docs.d.ts.map +1 -1
  24. package/dist/core/docs.js +93 -74
  25. package/dist/core/docs.js.map +1 -1
  26. package/dist/core/health.d.ts +6 -0
  27. package/dist/core/health.d.ts.map +1 -1
  28. package/dist/core/health.js +78 -21
  29. package/dist/core/health.js.map +1 -1
  30. package/dist/core/hooks.d.ts.map +1 -1
  31. package/dist/core/hooks.js +17 -19
  32. package/dist/core/hooks.js.map +1 -1
  33. package/dist/core/list.d.ts.map +1 -1
  34. package/dist/core/list.js +4 -7
  35. package/dist/core/list.js.map +1 -1
  36. package/dist/core/pr.d.ts.map +1 -1
  37. package/dist/core/pr.js +0 -8
  38. package/dist/core/pr.js.map +1 -1
  39. package/dist/core/risk-register.d.ts +17 -0
  40. package/dist/core/risk-register.d.ts.map +1 -0
  41. package/dist/core/risk-register.js +73 -0
  42. package/dist/core/risk-register.js.map +1 -0
  43. package/dist/core/shared-setup.d.ts.map +1 -1
  44. package/dist/core/shared-setup.js +5 -4
  45. package/dist/core/shared-setup.js.map +1 -1
  46. package/dist/core/status.d.ts.map +1 -1
  47. package/dist/core/status.js +3 -3
  48. package/dist/core/status.js.map +1 -1
  49. package/dist/core/trace.d.ts.map +1 -1
  50. package/dist/core/trace.js +37 -35
  51. package/dist/core/trace.js.map +1 -1
  52. package/dist/core/update.d.ts.map +1 -1
  53. package/dist/core/update.js +1 -10
  54. package/dist/core/update.js.map +1 -1
  55. package/dist/index.d.ts +0 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +0 -3
  58. package/dist/index.js.map +1 -1
  59. package/package.json +19 -2
  60. package/skills/grimoire-apply/SKILL.md +40 -37
  61. package/skills/grimoire-audit/SKILL.md +4 -1
  62. package/skills/grimoire-bug/SKILL.md +7 -3
  63. package/skills/grimoire-commit/SKILL.md +1 -1
  64. package/skills/grimoire-design/SKILL.md +3 -3
  65. package/skills/grimoire-discover/SKILL.md +77 -110
  66. package/skills/grimoire-draft/SKILL.md +55 -18
  67. package/skills/grimoire-plan/SKILL.md +58 -52
  68. package/skills/grimoire-pr/SKILL.md +7 -8
  69. package/skills/grimoire-pr-review/SKILL.md +2 -1
  70. package/skills/grimoire-refactor/SKILL.md +3 -3
  71. package/skills/grimoire-review/SKILL.md +13 -1
  72. package/skills/grimoire-verify/SKILL.md +19 -7
  73. package/skills/grimoire-vuln-remediate/SKILL.md +107 -0
  74. package/skills/grimoire-vuln-triage/SKILL.md +109 -0
  75. package/skills/references/artifact-map.md +44 -0
  76. package/skills/references/code-quality.md +41 -9
  77. package/skills/references/container-scan-triage.md +102 -0
  78. package/skills/references/dependency-vuln-triage.md +236 -0
  79. package/skills/references/principles.md +82 -0
  80. package/skills/references/refactor-scan-categories.md +2 -2
  81. package/skills/references/review-personas.md +13 -6
  82. package/skills/references/test-baseline.md +55 -0
  83. package/skills/references/testing-contracts.md +1 -1
  84. package/templates/accepted-risks.yml +47 -0
  85. package/templates/constraints.md +25 -0
  86. package/dist/commands/archive.d.ts +0 -3
  87. package/dist/commands/archive.d.ts.map +0 -1
  88. package/dist/commands/archive.js +0 -22
  89. package/dist/commands/archive.js.map +0 -1
  90. package/dist/commands/log.d.ts +0 -3
  91. package/dist/commands/log.d.ts.map +0 -1
  92. package/dist/commands/log.js +0 -15
  93. package/dist/commands/log.js.map +0 -1
  94. package/dist/commands/map.d.ts +0 -3
  95. package/dist/commands/map.d.ts.map +0 -1
  96. package/dist/commands/map.js +0 -16
  97. package/dist/commands/map.js.map +0 -1
  98. package/dist/core/archive.d.ts +0 -9
  99. package/dist/core/archive.d.ts.map +0 -1
  100. package/dist/core/archive.js +0 -81
  101. package/dist/core/archive.js.map +0 -1
  102. package/dist/core/log.d.ts +0 -8
  103. package/dist/core/log.d.ts.map +0 -1
  104. package/dist/core/log.js +0 -140
  105. package/dist/core/log.js.map +0 -1
  106. package/dist/core/map.d.ts +0 -22
  107. package/dist/core/map.d.ts.map +0 -1
  108. package/dist/core/map.js +0 -365
  109. package/dist/core/map.js.map +0 -1
  110. package/templates/dupignore +0 -93
  111. package/templates/mapignore +0 -58
  112. package/templates/mapkeys +0 -65
@@ -0,0 +1,82 @@
1
+ # Grimoire Design Principles
2
+
3
+ Four principles govern every grimoire artifact and every change. `grimoire-draft`,
4
+ `grimoire-plan`, and `grimoire-review` each enforce them at their own stage. They
5
+ are not style preferences — they are gates. A draft, plan, or design that violates
6
+ one without a stated reason is rejected, not merged.
7
+
8
+ This file is the single home for the principles (it practices what it preaches —
9
+ the skills cite it rather than restating it).
10
+
11
+ ---
12
+
13
+ ## 1. One right way to do a thing
14
+
15
+ There is exactly **one** sanctioned way to do each thing in the codebase, and one
16
+ authoritative home for each fact. Two ways to do the same thing is a defect, even
17
+ if both work.
18
+
19
+ - One capability → one feature spec. One decision → one MADR. One constraint → one
20
+ register entry. One fact → one home. No capability described in three places.
21
+ - When a second mechanism appears for an existing job, the right move is to delete
22
+ one and converge — never to keep both "for flexibility."
23
+ - **Tell:** "we could do it this way *or* that way" in a spec/plan. Pick one. Record
24
+ why in a MADR if the choice is non-obvious; don't leave both paths in the code.
25
+
26
+ ## 2. DRY — don't repeat yourself
27
+
28
+ Every piece of knowledge has a single, unambiguous representation.
29
+
30
+ - Don't store what's derivable. Code structure comes from codebase-memory-mcp on
31
+ demand — never freeze it into a doc that drifts. Generated overviews regenerate;
32
+ they are not hand-edited.
33
+ - Reuse before write: search the graph for an existing function/utility before
34
+ writing a new one. Three near-identical copies is the trigger to converge — but
35
+ do not abstract before the third (see KISS).
36
+ - Duplication of *content* (the same rule in three skill files, the same constant in
37
+ three modules, the same scenario in feature + MADR) is the target. Eliminate it.
38
+
39
+ ## 3. Don't reinvent the wheel — use existing tools
40
+
41
+ If an established tool already does a job well, use it. Do not build a parallel
42
+ grimoire mechanism that duplicates it.
43
+
44
+ - **git** is the wheel for change processes: branches = isolation, `git diff` =
45
+ staging, `git log` + PR + commit trailers = history and change identity. Do not
46
+ build change-folder copies, promote/sync steps, or bespoke archive/changelog trees.
47
+ - For auth, crypto, parsing, HTTP, queues, etc. — adopt the battle-tested library.
48
+ Never roll custom crypto, custom session management, custom auth tokens.
49
+ - Before building any tracking/versioning/state/diff mechanism, ask: does a standard
50
+ tool already in the stack do this? If yes, wire to it.
51
+ - **Exception that proves the rule:** when no single standard tool exists (e.g. issue
52
+ tracking is a fractured landscape), don't force-adopt one *and* don't build a
53
+ general-purpose clone. Keep any local mechanism narrow and purpose-scoped.
54
+
55
+ ## 4. Keep it simple (KISS)
56
+
57
+ The simplest thing that fully solves the *stated* problem wins.
58
+
59
+ - Least code, fewest new files, smallest surface area. A few lines in an existing
60
+ file beats a new module. A standard-library call beats a new dependency. Inline
61
+ beats a one-line wrapper.
62
+ - No premature abstraction. No `BaseX`/factory/strategy/config-object for a single
63
+ caller. No speculative generality "for a future second caller" that doesn't exist.
64
+ - Solve the problem in front of you, not the imagined one. Non-goals are real scope
65
+ boundaries — do not plan or build past them.
66
+ - **Tell:** an abstraction, indirection, or dependency whose only justification is a
67
+ hypothetical. Cut it.
68
+
69
+ ---
70
+
71
+ ## How the stages apply these
72
+
73
+ - **draft** — admission-test every artifact: does this fact already have a home
74
+ (one-right-way/DRY)? Is it behavior (→ feature) or a constraint/decision/structure
75
+ (→ its own home, not a feature)? Is there an existing tool/library for it
76
+ (don't-reinvent)? Is the scope the stated problem only (KISS)?
77
+ - **plan** — every task names the single approach (one-right-way), reuses before
78
+ writing (DRY), follows a proven pattern / existing tool rather than a bespoke one
79
+ (don't-reinvent), and chooses the least-code option within non-goals (KISS). Flag
80
+ any task that adds an abstraction, dependency, or second mechanism.
81
+ - **review** — a dedicated principles pass: hunt for duplicate homes, derivable-but-
82
+ stored facts, reinvented wheels, and speculative complexity. Each is a finding.
@@ -91,8 +91,8 @@ TODO/FIXME/HACK/XXX comments that have aged.
91
91
  ## 2g. Duplication
92
92
 
93
93
  **How to scan:**
94
- - Read `.grimoire/docs/.snapshot.json` `duplicates` section if present
95
- - Or run `config.tools.duplicates` if configured (e.g., jscpd)
94
+ - Run `config.tools.duplicates` if configured (e.g., jscpd), or `grimoire health` (config-driven `duplicates` metric)
95
+ - Plus semantic clones via `search_graph(semantic_query=[...])` (requires codebase-memory-mcp) to catch re-implementations under different names
96
96
  - Group by area — within-area dupes are easy to consolidate
97
97
 
98
98
  **Severity:** high = >30 lines or >3 copies, medium = 10-30 lines or 2 copies, low = <10 lines
@@ -26,13 +26,13 @@ Build once, inject as preface to every persona. Findings that don't threaten any
26
26
  - `.grimoire/brand/tokens.json` and `.grimoire/brand/voice.md` — brand axis (if exist; see `./brand-tokens-format.md`)
27
27
  - `.grimoire/changes/<id>/consult.md` — pre-design consult assumptions + givens (if exists)
28
28
  - `.grimoire/changes/<id>/designs/problem.md` — design problem statement (if exists)
29
- - Tag histogram across `.grimoire/changes/**/*.feature` + `.grimoire/archive/**/*.feature`
29
+ - Tag histogram across `features/**/*.feature` (the live baseline; features are edited in place, so this covers both the change and prior work)
30
30
  - All `.grimoire/decisions/*.md` with `status: accepted` — extract ID, title, top Decision Driver
31
31
  - Linked manifest's `Why` and `Non-goals` (if a Change trailer / active manifest exists); else PR body or commit messages
32
32
 
33
33
  ### Feature inventory
34
34
 
35
- - Glob `.grimoire/changes/**/*.feature` + `.grimoire/archive/**/*.feature`
35
+ - Glob `features/**/*.feature` (the live baseline, including this change's edits)
36
36
  - Parse: `Feature:` line, first description line, `@tags`
37
37
  - Bucket by path prefix (area)
38
38
  - If total >80, emit area-level summary only (count + capability one-liner)
@@ -227,12 +227,18 @@ Every security finding gets OWASP 2021 + CWE tags. See CWE quick-reference in `.
227
227
 
228
228
  Skip if change is purely internal.
229
229
 
230
+ **Coverage-gap routing (apply before recommending any new artifact).** A coverage gap does NOT default to "write a `.feature`." Route each gap to its one home using the feature-file admission test in `../grimoire-draft/SKILL.md` (§ jurisdiction table + the four admission gates):
231
+ - A `.feature` scenario is warranted **only** for an actor-observable behavior that passes all four gates (external actor, observable outcome, domain language, survives reimplementation).
232
+ - An invariant — observability/logging guarantee, perf budget, security control, compliance rule — is a **constraint**. Recommend it be recorded/verified in `.grimoire/docs/constraints.md`, never as a new `.feature`.
233
+ - When a behavior gap belongs in features, the default is **extend an existing feature file** in the same domain — recommend a new file only if no existing file fits, and say which were considered. Don't propose a `.feature` per finding.
234
+ - Test gaps for already-specified behavior are a *missing test*, not a missing feature — recommend the test, not a new spec.
235
+
230
236
  Evaluate:
231
- - **Test presence**: Every new user-facing behavior has a test? Every scenario from linked feature file has step definitions?
237
+ - **Test presence**: Every new user-facing behavior has a test? Every scenario from a linked feature file has step definitions? Missing test = recommend the test; only recommend a new/extended `.feature` if the behavior itself is unspecified *and* passes the admission test.
232
238
  - **Test quality**: Tests asserting outputs, or just that code "ran"? Over-mocked tests = red flag.
233
239
  - **Negative paths**: For each happy path, is there a failure-path test?
234
- - **Edge cases**: Empty states, concurrent users, interruptions, boundary values?
235
- - **Observability**: New feature — how will it be debugged in prod? Structured logs / metrics / error surfaces?
240
+ - **Edge cases**: Empty states, concurrent users, interruptions, boundary values? A missing edge case is a missing test/scenario in the relevant existing feature — not grounds for a new feature file.
241
+ - **Observability**: New feature — how will it be debugged in prod? Structured logs / metrics / error surfaces? Observability is a **constraint**: verify it's asserted in `constraints.md`; do not recommend a `.feature` for it.
236
242
  - **Regression risk** *(PR/pre-commit)*: Which existing tests cover the touched code? Were any tests removed or weakened?
237
243
  - **Accessibility**: New UI — keyboard nav, aria labels, contrast?
238
244
 
@@ -274,7 +280,8 @@ If none of the above pin a rule, **don't invent one**. Style preferences without
274
280
  - **Imports**: Order, grouping, and form match the project (relative vs absolute, `.js` extension policy, type-only imports)?
275
281
  - **Formatting**: Diff respects `.editorconfig` and formatter rules (indentation, line endings, trailing newline, max line length)? Any formatter-noisy hunks unrelated to the change?
276
282
  - **Comments — presence**: Is there a comment whose WHAT is already obvious from the code? Per most projects' comment policies (and grimoire's `AGENTS.md`), explanatory-of-what comments are noise — **suggestion** to remove.
277
- - **Comments — content**: Do comments reference current task / fix / PR / caller ("added for X", "used by Y", "fix for issue #123")? These rot — **suggestion** to remove or rewrite as durable rationale.
283
+ - **Comments — self-contained**: Does a comment name an external artifact that moves independently — a feature/scenario/`.feature`, MADR/ADR number, change-id, ticket/PR, specific test, or tag code (`LOG-OBS-003`)? These orphan — **suggestion** to rewrite in terms of the code itself (`code-quality.md` §7). Generic descriptive words (test, feature) are fine; identifiers pointing elsewhere are not.
284
+ - **Comments — no essays**: Does a docstring lead with a prose paragraph before its params, or restate types in prose? Keep the summary to 1–2 terse lines — **suggestion** to compress; design rationale belongs in a decision record.
278
285
  - **Comments — style**: Match the project's comment form (`//` vs `/* */` vs `#`, JSDoc/TSDoc/docstring conventions)?
279
286
  - **Docstrings**: New public functions/classes — does the project require docstrings? If yes (per `comment_style` or visible convention), missing docstring = **suggestion**. If no, added boilerplate docstrings = **suggestion** to remove.
280
287
  - **Dead comments**: Commented-out code in the diff = **suggestion** to delete.
@@ -0,0 +1,55 @@
1
+ # Test Baseline Reference
2
+
3
+ Loaded by skills that mutate code (`grimoire-apply`, `grimoire-bug`, `grimoire-refactor`) and the skill that checks for regressions (`grimoire-verify`).
4
+
5
+ ## Why
6
+
7
+ "That's a pre-existing failure" is unfalsifiable if you never recorded what was failing *before* you started. Without a baseline, verify diffs against nothing — a regression you introduced and a failure that was already red look identical, and the user finds out at the end instead of signing off at the start.
8
+
9
+ The fix is cheap: you already run the suite when you pick up a change. **Capture which tests were already failing, save it, and let the user accept it before any code is touched.** Verify then flags only *new* failures as regressions.
10
+
11
+ This is not a new gate. It's saving the result of a run you already do.
12
+
13
+ ## Capture (at the start of a code change)
14
+
15
+ Do this once, before writing the first test or touching production code — as part of the suite run you'd do anyway to understand the starting state.
16
+
17
+ 1. Run the configured suites: `config.tools.unit_test` and `config.tools.bdd_test`. Use what's configured; don't invent commands.
18
+ 2. Record the result to `.grimoire/changes/<change-id>/baseline.md` (ephemeral scaffolding, discarded with the change folder like `tasks.md`). For a bug fix with no change folder, record inline in the commit/test note and present to the user instead.
19
+ 3. Present the pre-existing failures to the user and get explicit acceptance before proceeding.
20
+
21
+ ### baseline.md format
22
+
23
+ ```markdown
24
+ # Test Baseline — <change-id>
25
+
26
+ captured: <date> # the day you ran it; if unavailable, omit
27
+ unit: <pass>/<total> passing command: <config.tools.unit_test>
28
+ bdd: <pass>/<total> passing command: <config.tools.bdd_test>
29
+
30
+ ## Pre-existing failures (accepted by user before change)
31
+ - <test id / name> — <one-line reason if known, else "pre-existing, cause unknown">
32
+ - ...
33
+
34
+ ## Notes
35
+ - <e.g. "unit suite not configured — baseline skipped for unit">
36
+ ```
37
+
38
+ If nothing was failing, say so explicitly: `## Pre-existing failures: none — clean baseline`.
39
+
40
+ ## Skip rules
41
+
42
+ - **No test command configured** for a suite → skip that suite, write `baseline skipped — no <suite> command configured` under Notes. Don't fabricate a command.
43
+ - **User opts out** → write `baseline skipped — user opted out`. Verify must then NOT claim a clean diff; it reports failures as unclassified (could be pre-existing or new).
44
+ - A skipped baseline is recorded, not silent. Verify needs to know it can't trust a diff.
45
+
46
+ ## Diff (at verify)
47
+
48
+ `grimoire-verify` reads `baseline.md` and classifies the current suite result against it:
49
+
50
+ - Test failing now **and** in baseline → **pre-existing** (already accepted; not a regression, don't blame the change).
51
+ - Test failing now, **not** in baseline → **regression** introduced by this change → CRITICAL, must fix before finalize.
52
+ - Test passing now, failing in baseline → incidentally fixed; note it, don't require it.
53
+ - **No baseline / baseline skipped** → state plainly that failures cannot be classified, and list them all for the user to triage. Do not assert "existing tests pass" or "pre-existing failure" without a baseline to back it.
54
+
55
+ The rule that replaces the old unfalsifiable claim: **a failure is "pre-existing" only if it's in `baseline.md`.** Otherwise it's yours.
@@ -41,7 +41,7 @@ For contract regression tests: if the client starts reading a new field or stops
41
41
  Before importing a module, calling a function, or adding a dependency — confirm it exists.
42
42
 
43
43
  **Imports and functions:**
44
- - Check area docs' Reusable Code table first (exact paths and line numbers)
44
+ - Query the graph first (`search_graph` / `get_code_snippet`) for the exact symbol, path, and signature
45
45
  - If importing from a file you haven't read, read it first
46
46
  - If an import fails, don't guess — read the actual module for the real export name
47
47
 
@@ -0,0 +1,47 @@
1
+ # Grimoire Risk-Accepted Vulnerabilities
2
+ # Vulnerabilities triaged as `affected` but consciously accepted — because no fix
3
+ # exists yet, the residual risk is low, or the fix isn't worth it right now.
4
+ #
5
+ # Written by: grimoire-vuln-remediate (from a grimoire-vuln-triage `accept` verdict)
6
+ # Read by: grimoire-vuln-triage — an UNEXPIRED entry auto-suppresses that CVE as
7
+ # known-accepted so it doesn't re-flood the queue. An EXPIRED entry is
8
+ # re-surfaced for re-triage.
9
+ #
10
+ # Each entry requires: a CVE, the component, the VEX justification, a reason, an
11
+ # owner, an accepted date, and an expiry (when to re-evaluate).
12
+ #
13
+ # An accepted risk is NOT closed — it is scheduled for review. Don't accept the same
14
+ # CVE twice; update the existing entry.
15
+ #
16
+ # Created by: grimoire init
17
+ # Last updated: YYYY-MM-DD
18
+
19
+ accepted: []
20
+
21
+ # --- Examples ---
22
+ #
23
+ # No upstream fix available — low residual risk, reachable but damped:
24
+ #
25
+ # - cve: CVE-2026-44405
26
+ # component: paramiko
27
+ # component_type: library
28
+ # vex_justification: inline_mitigations_already_exist
29
+ # reason: "SHA-1 accepted in rsakey; outbound SFTP only, to tenant-configured
30
+ # trusted hosts. No upstream fix. EPSS ~0, not in KEV. Blast radius:
31
+ # low (integration, not the public API)."
32
+ # owner: fred
33
+ # accepted: 2026-06-04
34
+ # expires: 2026-09-04
35
+ #
36
+ # Base-image OS package with no patched version yet (not removable — transitive):
37
+ #
38
+ # - cve: CVE-2026-40356
39
+ # component: krb5 (libgssapi-krb5-2, libk5crypto3, libkrb5-3, libkrb5support0)
40
+ # component_type: os-package
41
+ # vex_justification: vulnerable_code_not_in_execute_path
42
+ # reason: "Kerberos DoS. No gssapi/kerberos usage in app; pulled transitively
43
+ # via libpq5 (Postgres). Not removable without breaking libpq.
44
+ # status=affected, no FixedVersion. Re-check on base-image bump."
45
+ # owner: fred
46
+ # accepted: 2026-06-04
47
+ # expires: 2026-09-04
@@ -0,0 +1,25 @@
1
+ # Constraints
2
+
3
+ > Invariants that must always hold. These are **not** behaviors (no external actor,
4
+ > not observable in a scenario) — they are guarantees. Anything that failed the
5
+ > feature-file admission test in `grimoire-draft` because it is a security control,
6
+ > NFR, performance budget, observability guarantee, or compliance rule lives here,
7
+ > **not** in a `.feature`.
8
+ >
9
+ > Each constraint is verified by a `unit-invariant` test (created at plan/apply),
10
+ > never by a Gherkin scenario. Keep this register narrow: assert, justify, point to
11
+ > the proof. Don't let it grow into an issue tracker — open work belongs in your
12
+ > tracker, not here.
13
+
14
+ | Constraint (assertion) | Rationale | How verified | Links |
15
+ |------------------------|-----------|--------------|-------|
16
+ | _e.g._ Log output never contains PII or secrets | Confidential data must not leak to logs/stdout | `tests/test_log_scrubbing.py::test_pii_redacted` | [ADR-0008](.grimoire/decisions/0008-...) |
17
+ | _e.g._ Every request is isolated to its tenant | Multi-tenant data separation | `tests/test_tenant_isolation.py` | — |
18
+
19
+ <!--
20
+ Add one row per constraint. Guidance:
21
+ - Assertion: a flat "X always holds" statement. No Given/When/Then.
22
+ - Rationale: why it matters, in one line.
23
+ - How verified: the exact test id that proves it. If none yet, write "TODO: unit-invariant test" — the plan stage will create it.
24
+ - Links: the MADR that decided it (don't restate the decision here — DRY).
25
+ -->
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare const archiveCommand: Command;
3
- //# sourceMappingURL=archive.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/commands/archive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,eAAO,MAAM,cAAc,SAgBvB,CAAC"}
@@ -1,22 +0,0 @@
1
- import { Command } from "commander";
2
- import { archiveChange, ArchiveError } from "../core/archive.js";
3
- import chalk from "chalk";
4
- export const archiveCommand = new Command("archive")
5
- .description("Archive a completed change")
6
- .argument("<change-id>", "Change to archive")
7
- .option("-y, --yes", "Skip confirmation prompt")
8
- .action(async (changeId, options) => {
9
- try {
10
- await archiveChange(changeId, {
11
- yes: options.yes ?? false,
12
- });
13
- }
14
- catch (err) {
15
- if (err instanceof ArchiveError) {
16
- console.error(chalk.red(err.message));
17
- process.exit(1);
18
- }
19
- throw err;
20
- }
21
- });
22
- //# sourceMappingURL=archive.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/commands/archive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KACjD,WAAW,CAAC,4BAA4B,CAAC;KACzC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;KAC5C,MAAM,CAAC,WAAW,EAAE,0BAA0B,CAAC;KAC/C,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,OAAO,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,QAAQ,EAAE;YAC5B,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,KAAK;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare const logCommand: Command;
3
- //# sourceMappingURL=log.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,eAAO,MAAM,UAAU,SAWnB,CAAC"}
@@ -1,15 +0,0 @@
1
- import { Command } from "commander";
2
- import { generateLog } from "../core/log.js";
3
- export const logCommand = new Command("log")
4
- .description("Generate change log from archived grimoire changes")
5
- .option("--from <ref>", "Start date or git tag (inclusive)")
6
- .option("--to <ref>", "End date or git tag (inclusive)")
7
- .option("--json", "Output as JSON")
8
- .action(async (options) => {
9
- await generateLog({
10
- from: options.from,
11
- to: options.to,
12
- json: options.json ?? false,
13
- });
14
- });
15
- //# sourceMappingURL=log.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC;KACzC,WAAW,CAAC,oDAAoD,CAAC;KACjE,MAAM,CAAC,cAAc,EAAE,mCAAmC,CAAC;KAC3D,MAAM,CAAC,YAAY,EAAE,iCAAiC,CAAC;KACvD,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,WAAW,CAAC;QAChB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK;KAC5B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare const mapCommand: Command;
3
- //# sourceMappingURL=map.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../../src/commands/map.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,eAAO,MAAM,UAAU,SAUnB,CAAC"}
@@ -1,16 +0,0 @@
1
- import { Command } from "commander";
2
- import { runMap, McpRequiredError } from "../core/map.js";
3
- export const mapCommand = new Command("map")
4
- .description("Detect drift between conventions files and the current codebase")
5
- .option("--duplicates", "Run jscpd to detect code duplication")
6
- .action(async (options) => {
7
- try {
8
- await runMap({ duplicates: options.duplicates ?? false });
9
- }
10
- catch (e) {
11
- if (e instanceof McpRequiredError)
12
- process.exit(1);
13
- throw e;
14
- }
15
- });
16
- //# sourceMappingURL=map.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"map.js","sourceRoot":"","sources":["../../src/commands/map.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC;KACzC,WAAW,CAAC,iEAAiE,CAAC;KAC9E,MAAM,CAAC,cAAc,EAAE,sCAAsC,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -1,9 +0,0 @@
1
- interface ArchiveOptions {
2
- yes: boolean;
3
- }
4
- export declare class ArchiveError extends Error {
5
- constructor(message: string);
6
- }
7
- export declare function archiveChange(changeId: string, options: ArchiveOptions): Promise<void>;
8
- export {};
9
- //# sourceMappingURL=archive.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/core/archive.ts"],"names":[],"mappings":"AAKA,UAAU,cAAc;IACtB,GAAG,EAAE,OAAO,CAAC;CACd;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAwCD,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -1,81 +0,0 @@
1
- import { readFile, mkdir, cp, rm } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import chalk from "chalk";
4
- import { findProjectRoot, resolveChangePath } from "../utils/paths.js";
5
- export class ArchiveError extends Error {
6
- constructor(message) {
7
- super(message);
8
- this.name = "ArchiveError";
9
- }
10
- }
11
- async function checkPendingTasks(changePath, yes) {
12
- try {
13
- const tasksContent = await readFile(join(changePath, "tasks.md"), "utf-8");
14
- const pending = tasksContent.match(/^- \[ \] .+$/gm) || [];
15
- if (pending.length > 0) {
16
- console.log(chalk.yellow(`Warning: ${pending.length} task(s) still pending.`));
17
- if (!yes)
18
- throw new ArchiveError("Use --yes to archive anyway, or complete tasks first.");
19
- }
20
- }
21
- catch (err) {
22
- if (err instanceof ArchiveError)
23
- throw err;
24
- // No tasks file — ok
25
- }
26
- }
27
- async function getUserConfirmation(changeId) {
28
- const readline = await import("node:readline/promises");
29
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
30
- const answer = await rl.question(`Archive change "${changeId}"? (y/N) `);
31
- rl.close();
32
- return answer.toLowerCase() === "y";
33
- }
34
- async function syncArtifactsToBaseline(changePath, root) {
35
- try {
36
- await cp(join(changePath, "features"), join(root, "features"), { recursive: true, force: true });
37
- console.log(` ${chalk.green("synced")} features to baseline`);
38
- }
39
- catch {
40
- // No proposed features
41
- }
42
- try {
43
- // TODO: handle sequential numbering for new decisions
44
- await cp(join(changePath, "decisions"), join(root, ".grimoire", "decisions"), { recursive: true, force: true });
45
- console.log(` ${chalk.green("synced")} decisions to baseline`);
46
- }
47
- catch {
48
- // No proposed decisions
49
- }
50
- }
51
- export async function archiveChange(changeId, options) {
52
- const root = await findProjectRoot();
53
- const changePath = resolveChangePath(root, changeId);
54
- try {
55
- await readFile(join(changePath, "manifest.md"), "utf-8");
56
- }
57
- catch {
58
- throw new ArchiveError(`Change "${changeId}" not found or missing manifest.`);
59
- }
60
- await checkPendingTasks(changePath, options.yes);
61
- if (!options.yes) {
62
- if (!(await getUserConfirmation(changeId))) {
63
- console.log("Cancelled.");
64
- return;
65
- }
66
- }
67
- await syncArtifactsToBaseline(changePath, root);
68
- const date = new Date().toISOString().split("T")[0];
69
- const archiveDir = join(root, ".grimoire", "archive", `${date}-${changeId}`);
70
- await mkdir(archiveDir, { recursive: true });
71
- await cp(join(changePath, "manifest.md"), join(archiveDir, "manifest.md"));
72
- try {
73
- await cp(join(changePath, "tasks.md"), join(archiveDir, "tasks.md"));
74
- }
75
- catch {
76
- // no tasks
77
- }
78
- await rm(changePath, { recursive: true });
79
- console.log(`\n${chalk.green("Archived")} ${changeId} → .grimoire/archive/${date}-${changeId}/`);
80
- }
81
- //# sourceMappingURL=archive.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/core/archive.ts"],"names":[],"mappings":"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"}
@@ -1,8 +0,0 @@
1
- interface LogOptions {
2
- from?: string;
3
- to?: string;
4
- json: boolean;
5
- }
6
- export declare function generateLog(options: LogOptions): Promise<void>;
7
- export {};
8
- //# sourceMappingURL=log.d.ts.map
@@ -1 +0,0 @@
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;AA+BD,wBAAsB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCpE"}
package/dist/core/log.js DELETED
@@ -1,140 +0,0 @@
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
- function printLogEntry(entry, currentMonth) {
7
- const month = entry.date.slice(0, 7);
8
- if (month !== currentMonth) {
9
- console.log(chalk.bold.underline(`\n${formatMonth(month)}\n`));
10
- }
11
- console.log(` ${chalk.dim(entry.date)} ${chalk.cyan(entry.changeId)}`);
12
- console.log(` ${entry.summary}`);
13
- if (entry.features.length > 0)
14
- console.log(` ${chalk.dim("Features:")} ${entry.features.join(", ")}`);
15
- if (entry.decisions.length > 0)
16
- console.log(` ${chalk.dim("Decisions:")} ${entry.decisions.join(", ")}`);
17
- if (entry.scenarios.length > 0) {
18
- const display = entry.scenarios.length <= 3
19
- ? entry.scenarios.join(", ")
20
- : `${entry.scenarios.slice(0, 3).join(", ")} +${entry.scenarios.length - 3} more`;
21
- console.log(` ${chalk.dim("Scenarios:")} ${display}`);
22
- }
23
- console.log();
24
- return month;
25
- }
26
- export async function generateLog(options) {
27
- const root = await findProjectRoot();
28
- const archiveDir = join(root, ".grimoire", "archive");
29
- let entries;
30
- try {
31
- entries = await readArchiveEntries(archiveDir);
32
- }
33
- catch {
34
- throw new Error("No archive found. No changes have been archived yet.");
35
- }
36
- if (entries.length === 0) {
37
- console.log(chalk.dim("No archived changes found."));
38
- return;
39
- }
40
- if (options.from || options.to) {
41
- const fromDate = options.from ? await resolveDate(root, options.from) : "";
42
- const toDate = options.to ? await resolveDate(root, options.to) : "9999-99-99";
43
- entries = entries.filter((e) => e.date >= fromDate && e.date <= toDate);
44
- }
45
- entries.sort((a, b) => b.date.localeCompare(a.date));
46
- if (options.json) {
47
- console.log(JSON.stringify(entries, null, 2));
48
- return;
49
- }
50
- console.log(chalk.bold("Grimoire Change Log\n"));
51
- let currentMonth = "";
52
- for (const entry of entries) {
53
- currentMonth = printLogEntry(entry, currentMonth);
54
- }
55
- console.log(chalk.dim(`${entries.length} change(s) total`));
56
- }
57
- async function readArchiveEntries(archiveDir) {
58
- const dirs = await readdir(archiveDir, { withFileTypes: true });
59
- const entries = [];
60
- for (const dir of dirs) {
61
- if (!dir.isDirectory())
62
- continue;
63
- // Directory name format: YYYY-MM-DD-<change-id>
64
- const match = dir.name.match(/^(\d{4}-\d{2}-\d{2})-(.+)$/);
65
- if (!match)
66
- continue;
67
- const [, date, changeId] = match;
68
- const manifestPath = join(archiveDir, dir.name, "manifest.md");
69
- let manifest;
70
- try {
71
- manifest = await readFile(manifestPath, "utf-8");
72
- }
73
- catch {
74
- continue;
75
- }
76
- entries.push({
77
- date,
78
- changeId,
79
- ...parseManifest(manifest),
80
- });
81
- }
82
- return entries;
83
- }
84
- function parseManifest(content) {
85
- // Extract title: # Change: <summary>
86
- const titleMatch = content.match(/^#\s+Change:\s*(.+)$/m);
87
- const summary = titleMatch ? titleMatch[1].trim() : "(no summary)";
88
- // Extract why section
89
- const whyMatch = content.match(/^##\s+Why\s*\n([\s\S]*?)(?=^##|\Z)/m);
90
- const why = whyMatch ? whyMatch[1].trim() : "";
91
- // Extract feature changes
92
- const features = [];
93
- const featurePattern = /\*\*(?:ADDED|MODIFIED|REMOVED)\*\*\s+`([^`]+\.feature)`/g;
94
- let m;
95
- while ((m = featurePattern.exec(content)) !== null) {
96
- features.push(m[1]);
97
- }
98
- // Extract decisions
99
- const decisions = [];
100
- const decisionPattern = /\*\*(?:ADDED|MODIFIED|SUPERSEDED)\*\*\s+`(\d{4}-[^`]+\.md)`/g;
101
- while ((m = decisionPattern.exec(content)) !== null) {
102
- decisions.push(m[1]);
103
- }
104
- // Extract scenarios
105
- const scenarios = [];
106
- const scenarioPattern = /"([^"]+)"/g;
107
- const scenarioSection = content.match(/^##\s+Scenarios\s+(?:Added|Modified)\s*\n([\s\S]*?)(?=^##|\Z)/gm);
108
- if (scenarioSection) {
109
- for (const section of scenarioSection) {
110
- while ((m = scenarioPattern.exec(section)) !== null) {
111
- scenarios.push(m[1]);
112
- }
113
- }
114
- }
115
- return { summary, why, features, decisions, scenarios };
116
- }
117
- async function resolveDate(root, ref) {
118
- if (/^\d{4}-\d{2}-\d{2}$/.test(ref)) {
119
- return ref;
120
- }
121
- // Try as a git tag
122
- try {
123
- const git = simpleGit(root);
124
- const stdout = await git.raw(["log", "-1", "--format=%aI", ref]);
125
- return stdout.trim().split("T")[0];
126
- }
127
- catch {
128
- console.error(chalk.yellow(`Warning: Could not resolve "${ref}" as a git tag or date. Using as-is.`));
129
- return ref;
130
- }
131
- }
132
- function formatMonth(yyyyMm) {
133
- const [year, month] = yyyyMm.split("-");
134
- const months = [
135
- "January", "February", "March", "April", "May", "June",
136
- "July", "August", "September", "October", "November", "December",
137
- ];
138
- return `${months[parseInt(month, 10) - 1]} ${year}`;
139
- }
140
- //# sourceMappingURL=log.js.map