@mmnto/cli 1.101.1 → 1.102.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.
Files changed (82) hide show
  1. package/dist/commands/doctor.d.ts +36 -6
  2. package/dist/commands/doctor.d.ts.map +1 -1
  3. package/dist/commands/doctor.js +27 -0
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +46 -1
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/ecl-gc.d.ts.map +1 -1
  8. package/dist/commands/ecl-gc.js +16 -4
  9. package/dist/commands/ecl-gc.js.map +1 -1
  10. package/dist/commands/eject.d.ts +37 -2
  11. package/dist/commands/eject.d.ts.map +1 -1
  12. package/dist/commands/eject.js +79 -9
  13. package/dist/commands/eject.js.map +1 -1
  14. package/dist/commands/eject.test.js +169 -7
  15. package/dist/commands/eject.test.js.map +1 -1
  16. package/dist/commands/gate-install.test.js +22 -1
  17. package/dist/commands/gate-install.test.js.map +1 -1
  18. package/dist/commands/init-detect.js +1 -1
  19. package/dist/commands/init-detect.js.map +1 -1
  20. package/dist/commands/init-detect.test.js +2 -1
  21. package/dist/commands/init-detect.test.js.map +1 -1
  22. package/dist/commands/init-templates.js +8 -8
  23. package/dist/commands/install-hooks.d.ts +16 -0
  24. package/dist/commands/install-hooks.d.ts.map +1 -1
  25. package/dist/commands/install-hooks.js +5 -1
  26. package/dist/commands/install-hooks.js.map +1 -1
  27. package/dist/commands/mail-cli-wiring.test.d.ts +15 -0
  28. package/dist/commands/mail-cli-wiring.test.d.ts.map +1 -0
  29. package/dist/commands/mail-cli-wiring.test.js +106 -0
  30. package/dist/commands/mail-cli-wiring.test.js.map +1 -0
  31. package/dist/commands/mail.d.ts +80 -0
  32. package/dist/commands/mail.d.ts.map +1 -1
  33. package/dist/commands/mail.js +241 -9
  34. package/dist/commands/mail.js.map +1 -1
  35. package/dist/commands/mail.test.js +359 -4
  36. package/dist/commands/mail.test.js.map +1 -1
  37. package/dist/commands/review-fan.d.ts.map +1 -1
  38. package/dist/commands/review-fan.js +32 -8
  39. package/dist/commands/review-fan.js.map +1 -1
  40. package/dist/commands/review-fan.test.js +153 -7
  41. package/dist/commands/review-fan.test.js.map +1 -1
  42. package/dist/commands/shield-generated.d.ts +145 -0
  43. package/dist/commands/shield-generated.d.ts.map +1 -0
  44. package/dist/commands/shield-generated.js +266 -0
  45. package/dist/commands/shield-generated.js.map +1 -0
  46. package/dist/commands/shield-generated.test.d.ts +2 -0
  47. package/dist/commands/shield-generated.test.d.ts.map +1 -0
  48. package/dist/commands/shield-generated.test.js +323 -0
  49. package/dist/commands/shield-generated.test.js.map +1 -0
  50. package/dist/commands/shield.d.ts +27 -5
  51. package/dist/commands/shield.d.ts.map +1 -1
  52. package/dist/commands/shield.js +116 -41
  53. package/dist/commands/shield.js.map +1 -1
  54. package/dist/commands/shield.test.js +92 -2
  55. package/dist/commands/shield.test.js.map +1 -1
  56. package/dist/commands/status.d.ts.map +1 -1
  57. package/dist/commands/status.js +26 -2
  58. package/dist/commands/status.js.map +1 -1
  59. package/dist/commands/status.test.js +55 -1
  60. package/dist/commands/status.test.js.map +1 -1
  61. package/dist/hook/schema.d.ts +2 -2
  62. package/dist/index.js +28 -7
  63. package/dist/index.js.map +1 -1
  64. package/dist/orchestrators/orchestrator.d.ts +86 -1
  65. package/dist/orchestrators/orchestrator.d.ts.map +1 -1
  66. package/dist/orchestrators/orchestrator.js +277 -9
  67. package/dist/orchestrators/orchestrator.js.map +1 -1
  68. package/dist/orchestrators/orchestrator.test.js +240 -4
  69. package/dist/orchestrators/orchestrator.test.js.map +1 -1
  70. package/dist/orchestrators/shell-orchestrator.d.ts +12 -1
  71. package/dist/orchestrators/shell-orchestrator.d.ts.map +1 -1
  72. package/dist/orchestrators/shell-orchestrator.js +256 -49
  73. package/dist/orchestrators/shell-orchestrator.js.map +1 -1
  74. package/dist/orchestrators/shell-orchestrator.test.js +252 -4
  75. package/dist/orchestrators/shell-orchestrator.test.js.map +1 -1
  76. package/dist/utils.d.ts +19 -1
  77. package/dist/utils.d.ts.map +1 -1
  78. package/dist/utils.js +394 -108
  79. package/dist/utils.js.map +1 -1
  80. package/dist/utils.test.js +406 -14
  81. package/dist/utils.test.js.map +1 -1
  82. package/package.json +2 -2
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Generated-artifact exclusion for the review synthesis input (mmnto-ai/totem#2398).
3
+ *
4
+ * `totem review` used to send generated-artifact bytes (lockfiles,
5
+ * `compiled-rules.json`, `dist/**`, `*.wasm`, regenerated dashboards) to the LLM
6
+ * as part of the review diff. The only exclusion mechanism was `ignorePatterns`
7
+ * — opt-in, empty by default, and a **silent drop**: an excluded file simply
8
+ * vanished from the payload, losing the signal that it changed at all.
9
+ *
10
+ * This module classifies generated artifacts by default and replaces their
11
+ * bytes with a per-file SUMMARY (path, change shape, size delta, semantic hash)
12
+ * injected into the synthesis input. The reviewer keeps the "this regenerated"
13
+ * signal without paying review-context tokens for bytes no reviewer should read.
14
+ *
15
+ * Cautionary sibling #2329: the injected summary is CLEARLY LABELLED as a
16
+ * Totem-generated summarization artifact (a distinct XML tag + a preamble that
17
+ * says "not diff content") so the model cannot mistake it for real diff content
18
+ * the way secret-redaction's env-var rewriting was mistaken for a real read.
19
+ *
20
+ * Aligns with the Prop 297 taxonomy (GENERATED spans take regeneration-diff
21
+ * treatment, never prose review — mmnto-ai/totem-strategy#639 operating spec).
22
+ */
23
+ /**
24
+ * The default generated-artifact classification for the review payload. Seeded
25
+ * globs covering the classes the issue names (lockfiles, `compiled-rules.json`,
26
+ * `dist/**`, `*.wasm`) plus the conventional build-output + compiled-web set.
27
+ *
28
+ * These globs use `matchesGlob` semantics (see `@mmnto/totem` `matchesGlob`):
29
+ * `**​/name` matches `name` at any depth, `**​/dir/**` matches a directory tree
30
+ * at any depth, `*.ext` matches an extension anywhere.
31
+ *
32
+ * A false positive here strips a real-code file from LLM review — but it is
33
+ * NEVER a silent drop: every excluded file is loudly named in the injected
34
+ * summary (path + hash), and a repo un-marks a false positive Git-natively via
35
+ * `.gitattributes` `linguist-generated=false` (honored below). Repos extend the
36
+ * set the same way (`path/to/thing linguist-generated`).
37
+ */
38
+ export declare const DEFAULT_GENERATED_ARTIFACT_GLOBS: readonly string[];
39
+ /**
40
+ * Patterns parsed from a repo's `.gitattributes` `linguist-generated` markers:
41
+ * - `generated` — patterns marked `linguist-generated` or `linguist-generated=true`
42
+ * (ADD to the classification).
43
+ * - `notGenerated` — patterns marked `linguist-generated=false` (an explicit
44
+ * un-mark that OVERRIDES a default-glob match — the Git-native escape hatch).
45
+ */
46
+ export interface GitattributesGeneratedPatterns {
47
+ generated: string[];
48
+ notGenerated: string[];
49
+ }
50
+ /**
51
+ * Translate a `.gitattributes` path pattern into a `matchesGlob`-compatible glob.
52
+ *
53
+ * Full gitattributes/gitignore pattern fidelity is out of scope; this covers the
54
+ * common forms the classification cares about:
55
+ * - a leading `/` (root-anchored) is stripped — `matchesGlob` treats a
56
+ * slash-bearing literal as repo-root-relative already;
57
+ * - a trailing `/` (directory) becomes `<dir>/**` so the tree is matched;
58
+ * - bare patterns (`*.lock`, `foo.map`) pass through — `matchesGlob` already
59
+ * matches those at any depth.
60
+ */
61
+ export declare function gitattributesPatternToGlob(pattern: string): string;
62
+ /**
63
+ * Parse `.gitattributes` at the repo root for `linguist-generated` markers.
64
+ * Returns empty pattern lists when the file is absent or unreadable (best-effort
65
+ * enrichment — a missing `.gitattributes` is never an error). Comment lines
66
+ * (`#…`) and macro lines (`[attr]…`) are ignored.
67
+ */
68
+ export declare function readGitattributesGeneratedPatterns(cwd: string): GitattributesGeneratedPatterns;
69
+ export type GeneratedChangeShape = 'added' | 'deleted' | 'regenerated';
70
+ /**
71
+ * A per-file summary of an excluded generated artifact. `addedLines` /
72
+ * `removedLines` are the unified-diff insertion/deletion counts (the available
73
+ * size-delta proxy, since the bytes themselves are excluded by design);
74
+ * `binary` marks a file git reported as a binary change (no line counts).
75
+ * `hash` is a semantic hash: sha256 (first 12 hex) over the file's own diff
76
+ * segment — a stable fingerprint of exactly what was excluded.
77
+ */
78
+ export interface GeneratedArtifactSummary {
79
+ file: string;
80
+ shape: GeneratedChangeShape;
81
+ addedLines: number;
82
+ removedLines: number;
83
+ binary: boolean;
84
+ hash: string;
85
+ }
86
+ export interface GeneratedArtifactClassification {
87
+ /** Repo-relative paths classified as generated artifacts (bytes excluded). */
88
+ artifactFiles: string[];
89
+ /** Per-file summaries, in diff order. */
90
+ summaries: GeneratedArtifactSummary[];
91
+ /** The diff with every generated-artifact section removed (byte-identical to the input when none matched). */
92
+ keptDiff: string;
93
+ /** `changedFiles` minus the generated artifacts. */
94
+ keptFiles: string[];
95
+ }
96
+ export interface ClassifyGeneratedArtifactsParams {
97
+ diff: string;
98
+ changedFiles: string[];
99
+ /** Positive globs (defaults + `.gitattributes` generated). Defaults to {@link DEFAULT_GENERATED_ARTIFACT_GLOBS}. */
100
+ generatedGlobs?: readonly string[];
101
+ /** Un-mark globs (`.gitattributes` `linguist-generated=false`) — a match here is NEVER a generated artifact. */
102
+ excludeGlobs?: readonly string[];
103
+ }
104
+ /**
105
+ * A single file's section of a unified diff, with its destination path.
106
+ * `file` is `null` for a leading preamble section (the text before the first
107
+ * `diff --git`, normally empty) or a section whose header does not parse.
108
+ */
109
+ interface DiffFileSection {
110
+ file: string | null;
111
+ section: string;
112
+ }
113
+ /**
114
+ * Split a unified diff into per-file sections at `diff --git` boundaries and
115
+ * extract each section's destination (`b/`) path. Mirrors the split + path
116
+ * extraction that `filterDiffByPatterns` / `extractChangedFiles` use so the two
117
+ * agree on file identity. Joining `section` values back reconstructs the input
118
+ * exactly (the zero-width split consumes no characters).
119
+ */
120
+ export declare function splitDiffIntoFileSections(diff: string): DiffFileSection[];
121
+ /** Semantic hash of an excluded artifact's diff segment: sha256, first 12 hex chars. */
122
+ export declare function hashDiffSection(section: string): string;
123
+ /**
124
+ * Classify the changed files into generated artifacts vs. kept files, remove the
125
+ * artifact sections from the diff, and build a per-file summary of each excluded
126
+ * artifact. A file is a generated artifact iff it matches ANY positive glob AND
127
+ * matches NO un-mark (exclude) glob.
128
+ *
129
+ * When no artifact matches, `keptDiff === diff` and `keptFiles === changedFiles`
130
+ * (order preserved) — the legacy review payload is byte-identical, so callers
131
+ * can gate the new path on `summaries.length > 0`.
132
+ */
133
+ export declare function classifyGeneratedArtifacts(params: ClassifyGeneratedArtifactsParams): GeneratedArtifactClassification;
134
+ /** One operator-visible / prompt line for an excluded artifact. Paths are sanitized. */
135
+ export declare function formatGeneratedArtifactLine(summary: GeneratedArtifactSummary): string;
136
+ /**
137
+ * Build the synthesis-input section for the excluded generated artifacts.
138
+ * Returns `''` when there are no artifacts (so the caller injects nothing on the
139
+ * legacy path). The body is wrapped in a distinct XML tag and the preamble
140
+ * states plainly that these lines are a Totem-generated summary, NOT diff
141
+ * content — the #2329 mislabel guard.
142
+ */
143
+ export declare function buildGeneratedArtifactSection(summaries: GeneratedArtifactSummary[]): string;
144
+ export {};
145
+ //# sourceMappingURL=shield-generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield-generated.d.ts","sourceRoot":"","sources":["../../src/commands/shield-generated.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gCAAgC,EAAE,SAAS,MAAM,EA4B7D,CAAC;AAOF;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlE;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,GAAG,EAAE,MAAM,GAAG,8BAA8B,CAmC9F;AAID,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAEvE;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,oBAAoB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,+BAA+B;IAC9C,8EAA8E;IAC9E,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,yCAAyC;IACzC,SAAS,EAAE,wBAAwB,EAAE,CAAC;IACtC,8GAA8G;IAC9G,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oHAAoH;IACpH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,gHAAgH;IAChH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED;;;;GAIG;AACH,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAgBzE;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AA0BD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,gCAAgC,GACvC,+BAA+B,CA+BjC;AAUD,wFAAwF;AACxF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAGrF;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,wBAAwB,EAAE,GAAG,MAAM,CAiB3F"}
@@ -0,0 +1,266 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ // Imported from the CLI's own `utils` re-export barrel (NOT '@mmnto/totem'
5
+ // directly) so this command module avoids a static top-level import of the
6
+ // heavy core barrel at startup (mmnto-ai/totem#2339).
7
+ import { matchesGlob, sanitize, wrapXml } from '../utils.js';
8
+ /**
9
+ * Generated-artifact exclusion for the review synthesis input (mmnto-ai/totem#2398).
10
+ *
11
+ * `totem review` used to send generated-artifact bytes (lockfiles,
12
+ * `compiled-rules.json`, `dist/**`, `*.wasm`, regenerated dashboards) to the LLM
13
+ * as part of the review diff. The only exclusion mechanism was `ignorePatterns`
14
+ * — opt-in, empty by default, and a **silent drop**: an excluded file simply
15
+ * vanished from the payload, losing the signal that it changed at all.
16
+ *
17
+ * This module classifies generated artifacts by default and replaces their
18
+ * bytes with a per-file SUMMARY (path, change shape, size delta, semantic hash)
19
+ * injected into the synthesis input. The reviewer keeps the "this regenerated"
20
+ * signal without paying review-context tokens for bytes no reviewer should read.
21
+ *
22
+ * Cautionary sibling #2329: the injected summary is CLEARLY LABELLED as a
23
+ * Totem-generated summarization artifact (a distinct XML tag + a preamble that
24
+ * says "not diff content") so the model cannot mistake it for real diff content
25
+ * the way secret-redaction's env-var rewriting was mistaken for a real read.
26
+ *
27
+ * Aligns with the Prop 297 taxonomy (GENERATED spans take regeneration-diff
28
+ * treatment, never prose review — mmnto-ai/totem-strategy#639 operating spec).
29
+ */
30
+ // ─── Default seeded globs ────────────────────────────────
31
+ /**
32
+ * The default generated-artifact classification for the review payload. Seeded
33
+ * globs covering the classes the issue names (lockfiles, `compiled-rules.json`,
34
+ * `dist/**`, `*.wasm`) plus the conventional build-output + compiled-web set.
35
+ *
36
+ * These globs use `matchesGlob` semantics (see `@mmnto/totem` `matchesGlob`):
37
+ * `**​/name` matches `name` at any depth, `**​/dir/**` matches a directory tree
38
+ * at any depth, `*.ext` matches an extension anywhere.
39
+ *
40
+ * A false positive here strips a real-code file from LLM review — but it is
41
+ * NEVER a silent drop: every excluded file is loudly named in the injected
42
+ * summary (path + hash), and a repo un-marks a false positive Git-natively via
43
+ * `.gitattributes` `linguist-generated=false` (honored below). Repos extend the
44
+ * set the same way (`path/to/thing linguist-generated`).
45
+ */
46
+ export const DEFAULT_GENERATED_ARTIFACT_GLOBS = [
47
+ // Dependency lockfiles — machine-regenerated, never meaningfully hand-authored.
48
+ '**/package-lock.json',
49
+ '**/pnpm-lock.yaml',
50
+ '**/yarn.lock',
51
+ '**/npm-shrinkwrap.json',
52
+ '**/bun.lockb',
53
+ '**/Cargo.lock',
54
+ '**/poetry.lock',
55
+ '**/composer.lock',
56
+ '**/Gemfile.lock',
57
+ '**/go.sum',
58
+ '**/flake.lock',
59
+ // Totem compiled artifacts (issue-named).
60
+ '**/.totem/compiled-rules.json',
61
+ '**/.totem/compile-manifest.json',
62
+ // Conventional build-output directories.
63
+ '**/dist/**',
64
+ '**/build/**',
65
+ '**/out/**',
66
+ '**/.next/**',
67
+ '**/coverage/**',
68
+ // Compiled / minified / binary web + WASM artifacts (issue-named `*.wasm`).
69
+ '**/*.wasm',
70
+ '**/*.min.js',
71
+ '**/*.min.css',
72
+ '**/*.map',
73
+ '**/*.tsbuildinfo',
74
+ ];
75
+ // ─── .gitattributes linguist-generated ──────────────────
76
+ /** Cap on the `.gitattributes` bytes read — a defensive bound, real files are tiny. */
77
+ const MAX_GITATTRIBUTES_BYTES = 256 * 1024;
78
+ /**
79
+ * Translate a `.gitattributes` path pattern into a `matchesGlob`-compatible glob.
80
+ *
81
+ * Full gitattributes/gitignore pattern fidelity is out of scope; this covers the
82
+ * common forms the classification cares about:
83
+ * - a leading `/` (root-anchored) is stripped — `matchesGlob` treats a
84
+ * slash-bearing literal as repo-root-relative already;
85
+ * - a trailing `/` (directory) becomes `<dir>/**` so the tree is matched;
86
+ * - bare patterns (`*.lock`, `foo.map`) pass through — `matchesGlob` already
87
+ * matches those at any depth.
88
+ */
89
+ export function gitattributesPatternToGlob(pattern) {
90
+ let p = pattern.trim();
91
+ if (p.startsWith('/'))
92
+ p = p.slice(1);
93
+ if (p.endsWith('/'))
94
+ p = `${p}**`;
95
+ return p;
96
+ }
97
+ /**
98
+ * Parse `.gitattributes` at the repo root for `linguist-generated` markers.
99
+ * Returns empty pattern lists when the file is absent or unreadable (best-effort
100
+ * enrichment — a missing `.gitattributes` is never an error). Comment lines
101
+ * (`#…`) and macro lines (`[attr]…`) are ignored.
102
+ */
103
+ export function readGitattributesGeneratedPatterns(cwd) {
104
+ const generated = [];
105
+ const notGenerated = [];
106
+ const file = path.join(cwd, '.gitattributes');
107
+ let raw;
108
+ try {
109
+ const stat = fs.statSync(file);
110
+ if (!stat.isFile() || stat.size > MAX_GITATTRIBUTES_BYTES) {
111
+ return { generated, notGenerated };
112
+ }
113
+ raw = fs.readFileSync(file, 'utf-8');
114
+ // totem-context: intentional cleanup — an absent / unreadable .gitattributes is honest-absent (the seeded default globs still apply); this best-effort enrichment read must never crash the review, so degrading to "no repo-declared patterns" is correct, not a swallowed error (mmnto-ai/totem#2398).
115
+ }
116
+ catch {
117
+ return { generated, notGenerated };
118
+ }
119
+ for (const line of raw.split(/\r?\n/)) {
120
+ const trimmed = line.trim();
121
+ if (trimmed.length === 0 || trimmed.startsWith('#') || trimmed.startsWith('['))
122
+ continue;
123
+ // A gitattributes line is `<pattern> <attr> <attr> …`. Split on whitespace;
124
+ // the first token is the pattern, the rest are attributes.
125
+ const tokens = trimmed.split(/\s+/);
126
+ const pattern = tokens[0];
127
+ if (pattern === undefined || pattern.length === 0)
128
+ continue;
129
+ const attrs = tokens.slice(1);
130
+ for (const attr of attrs) {
131
+ if (attr === 'linguist-generated' || attr === 'linguist-generated=true') {
132
+ generated.push(gitattributesPatternToGlob(pattern));
133
+ }
134
+ else if (attr === '-linguist-generated' || attr === 'linguist-generated=false') {
135
+ // `-attr` and `attr=false` both UNSET the attribute in git's model.
136
+ notGenerated.push(gitattributesPatternToGlob(pattern));
137
+ }
138
+ }
139
+ }
140
+ return { generated, notGenerated };
141
+ }
142
+ /**
143
+ * Split a unified diff into per-file sections at `diff --git` boundaries and
144
+ * extract each section's destination (`b/`) path. Mirrors the split + path
145
+ * extraction that `filterDiffByPatterns` / `extractChangedFiles` use so the two
146
+ * agree on file identity. Joining `section` values back reconstructs the input
147
+ * exactly (the zero-width split consumes no characters).
148
+ */
149
+ export function splitDiffIntoFileSections(diff) {
150
+ const rawSections = diff.split(/^(?=diff --git )/m);
151
+ return rawSections
152
+ .filter((section) => section.length > 0)
153
+ .map((section) => {
154
+ // A section with no newline at all (a truncated diff tail) must use the
155
+ // WHOLE section as its first line — indexOf's -1 would otherwise slice
156
+ // off the final character and break the $-anchored filename match (GCA
157
+ // round on #2443).
158
+ const newlineIdx = section.indexOf('\n');
159
+ const firstLine = newlineIdx === -1 ? section : section.slice(0, newlineIdx);
160
+ const quoted = firstLine.match(/^diff --git "a\/.*?" "b\/(.*?)"$/);
161
+ const unquoted = firstLine.match(/^diff --git a\/\S+ b\/(.+)$/);
162
+ const file = quoted?.[1] ?? unquoted?.[1] ?? null;
163
+ return { file, section };
164
+ });
165
+ }
166
+ /** Semantic hash of an excluded artifact's diff segment: sha256, first 12 hex chars. */
167
+ export function hashDiffSection(section) {
168
+ return crypto.createHash('sha256').update(section, 'utf-8').digest('hex').slice(0, 12);
169
+ }
170
+ /** Summarize one generated-artifact diff section (shape + size delta + hash). */
171
+ function summarizeSection(file, section) {
172
+ const shape = /^new file mode /m.test(section)
173
+ ? 'added'
174
+ : /^deleted file mode /m.test(section)
175
+ ? 'deleted'
176
+ : 'regenerated';
177
+ const binary = /^Binary files /m.test(section) || /^GIT binary patch/m.test(section);
178
+ let addedLines = 0;
179
+ let removedLines = 0;
180
+ if (!binary) {
181
+ for (const line of section.split('\n')) {
182
+ // Skip the `+++ `/`--- ` file-header lines (always a space or /dev/null
183
+ // after the triple marker); a bare `+++`/`---` prefix check would also
184
+ // swallow real hunk content like `++i;` → `+++i;` (GCA round on #2443).
185
+ if (line.startsWith('+') && !line.startsWith('+++ '))
186
+ addedLines++;
187
+ else if (line.startsWith('-') && !line.startsWith('--- '))
188
+ removedLines++;
189
+ }
190
+ }
191
+ return { file, shape, addedLines, removedLines, binary, hash: hashDiffSection(section) };
192
+ }
193
+ /**
194
+ * Classify the changed files into generated artifacts vs. kept files, remove the
195
+ * artifact sections from the diff, and build a per-file summary of each excluded
196
+ * artifact. A file is a generated artifact iff it matches ANY positive glob AND
197
+ * matches NO un-mark (exclude) glob.
198
+ *
199
+ * When no artifact matches, `keptDiff === diff` and `keptFiles === changedFiles`
200
+ * (order preserved) — the legacy review payload is byte-identical, so callers
201
+ * can gate the new path on `summaries.length > 0`.
202
+ */
203
+ export function classifyGeneratedArtifacts(params) {
204
+ const generatedGlobs = params.generatedGlobs ?? DEFAULT_GENERATED_ARTIFACT_GLOBS;
205
+ const excludeGlobs = params.excludeGlobs ?? [];
206
+ const isGenerated = (file) => generatedGlobs.some((g) => matchesGlob(file, g)) &&
207
+ !excludeGlobs.some((g) => matchesGlob(file, g));
208
+ const artifactSet = new Set();
209
+ const summaries = [];
210
+ const keptSections = [];
211
+ for (const { file, section } of splitDiffIntoFileSections(params.diff)) {
212
+ if (file !== null && isGenerated(file)) {
213
+ artifactSet.add(file);
214
+ summaries.push(summarizeSection(file, section));
215
+ }
216
+ else {
217
+ keptSections.push(section);
218
+ }
219
+ }
220
+ // Derive kept files from the ORIGINAL changedFiles (preserves order + covers
221
+ // any file the diff-section parse might miss), subtracting the classified set.
222
+ const keptFiles = params.changedFiles.filter((f) => !artifactSet.has(f));
223
+ return {
224
+ artifactFiles: [...artifactSet],
225
+ summaries,
226
+ keptDiff: keptSections.join(''), // totem-ignore (#669) — joining diff sections, not text fragments
227
+ keptFiles,
228
+ };
229
+ }
230
+ // ─── Summary rendering ───────────────────────────────────
231
+ const SHAPE_LABEL = {
232
+ added: 'added',
233
+ deleted: 'deleted',
234
+ regenerated: 'regenerated',
235
+ };
236
+ /** One operator-visible / prompt line for an excluded artifact. Paths are sanitized. */
237
+ export function formatGeneratedArtifactLine(summary) {
238
+ const delta = summary.binary ? 'binary' : `+${summary.addedLines}/-${summary.removedLines} lines`;
239
+ return `- ${sanitize(summary.file)} — ${SHAPE_LABEL[summary.shape]}, ${delta}, hash ${summary.hash}`;
240
+ }
241
+ /**
242
+ * Build the synthesis-input section for the excluded generated artifacts.
243
+ * Returns `''` when there are no artifacts (so the caller injects nothing on the
244
+ * legacy path). The body is wrapped in a distinct XML tag and the preamble
245
+ * states plainly that these lines are a Totem-generated summary, NOT diff
246
+ * content — the #2329 mislabel guard.
247
+ */
248
+ export function buildGeneratedArtifactSection(summaries) {
249
+ if (summaries.length === 0)
250
+ return '';
251
+ const lines = summaries.map(formatGeneratedArtifactLine).join('\n');
252
+ const preamble = 'The files below changed but their BYTES WERE EXCLUDED from this review because ' +
253
+ 'they are generated artifacts (lockfiles, compiled outputs, build products). ' +
254
+ 'This block is a TOTEM-GENERATED SUMMARY, not diff content — do NOT treat these ' +
255
+ 'lines as code under review, and do NOT emit findings about them. Their ' +
256
+ 'correctness is the job of deterministic regeneration gates (e.g. `totem lint`, ' +
257
+ 'lockfile-sync), not prose review. Each line is: path — change shape, size ' +
258
+ 'delta (unified-diff insert/delete counts), semantic hash (sha256/12 of the ' +
259
+ "excluded file's diff segment).";
260
+ return [
261
+ '\n=== EXCLUDED GENERATED ARTIFACTS (TOTEM SUMMARY — NOT DIFF CONTENT) ===',
262
+ preamble,
263
+ wrapXml('generated_artifacts_summary', lines),
264
+ ].join('\n');
265
+ }
266
+ //# sourceMappingURL=shield-generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield-generated.js","sourceRoot":"","sources":["../../src/commands/shield-generated.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shield-generated.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield-generated.test.d.ts","sourceRoot":"","sources":["../../src/commands/shield-generated.test.ts"],"names":[],"mappings":""}