kibi-cli 0.8.0 → 0.10.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.
Files changed (70) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +11 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +229 -4
  5. package/dist/commands/init-helpers.d.ts.map +1 -1
  6. package/dist/commands/init-helpers.js +11 -14
  7. package/dist/commands/migrate.d.ts +9 -0
  8. package/dist/commands/migrate.d.ts.map +1 -0
  9. package/dist/commands/migrate.js +183 -0
  10. package/dist/commands/sync/manifest.d.ts +8 -2
  11. package/dist/commands/sync/manifest.d.ts.map +1 -1
  12. package/dist/commands/sync/manifest.js +56 -11
  13. package/dist/commands/sync.d.ts +1 -0
  14. package/dist/commands/sync.d.ts.map +1 -1
  15. package/dist/commands/sync.js +9 -7
  16. package/dist/extractors/manifest.d.ts +30 -0
  17. package/dist/extractors/manifest.d.ts.map +1 -1
  18. package/dist/extractors/manifest.js +60 -7
  19. package/dist/extractors/symbol-coordinates.d.ts +15 -0
  20. package/dist/extractors/symbol-coordinates.d.ts.map +1 -0
  21. package/dist/extractors/symbol-coordinates.js +83 -0
  22. package/dist/public/check-types.d.ts +3 -1
  23. package/dist/public/check-types.d.ts.map +1 -1
  24. package/dist/public/check-types.js +1 -0
  25. package/dist/public/extractors/manifest.d.ts +1 -1
  26. package/dist/public/extractors/manifest.d.ts.map +1 -1
  27. package/dist/public/extractors/manifest.js +1 -1
  28. package/dist/public/ignore-policy.d.ts +10 -0
  29. package/dist/public/ignore-policy.d.ts.map +1 -0
  30. package/dist/public/ignore-policy.js +219 -0
  31. package/dist/public/schema-version.d.ts +3 -0
  32. package/dist/public/schema-version.d.ts.map +1 -0
  33. package/dist/public/schema-version.js +1 -0
  34. package/dist/traceability/evidence-model.d.ts +142 -0
  35. package/dist/traceability/evidence-model.d.ts.map +1 -0
  36. package/dist/traceability/evidence-model.js +70 -0
  37. package/dist/traceability/git-staged.d.ts +1 -0
  38. package/dist/traceability/git-staged.d.ts.map +1 -1
  39. package/dist/traceability/git-staged.js +28 -3
  40. package/dist/traceability/staged-diagnostics.d.ts +25 -0
  41. package/dist/traceability/staged-diagnostics.d.ts.map +1 -0
  42. package/dist/traceability/staged-diagnostics.js +67 -0
  43. package/dist/traceability/staged-impact-contract.d.ts +57 -0
  44. package/dist/traceability/staged-impact-contract.d.ts.map +1 -0
  45. package/dist/traceability/staged-impact-contract.js +202 -0
  46. package/dist/traceability/staged-symbols-manifest.d.ts +23 -0
  47. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -0
  48. package/dist/traceability/staged-symbols-manifest.js +269 -0
  49. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  50. package/dist/traceability/symbol-extract.js +33 -9
  51. package/dist/utils/config.d.ts +1 -0
  52. package/dist/utils/config.d.ts.map +1 -1
  53. package/dist/utils/config.js +35 -22
  54. package/dist/utils/manifest-paths.d.ts +8 -0
  55. package/dist/utils/manifest-paths.d.ts.map +1 -0
  56. package/dist/utils/manifest-paths.js +62 -0
  57. package/dist/utils/rule-registry.d.ts.map +1 -1
  58. package/dist/utils/rule-registry.js +6 -0
  59. package/dist/utils/schema-version.d.ts +14 -0
  60. package/dist/utils/schema-version.d.ts.map +1 -0
  61. package/dist/utils/schema-version.js +59 -0
  62. package/dist/utils/strict-modeling.d.ts +64 -0
  63. package/dist/utils/strict-modeling.d.ts.map +1 -0
  64. package/dist/utils/strict-modeling.js +371 -0
  65. package/package.json +13 -3
  66. package/schema/config.json +8 -1
  67. package/src/public/check-types.ts +15 -1
  68. package/src/public/extractors/manifest.ts +2 -0
  69. package/src/public/ignore-policy.ts +229 -0
  70. package/src/public/schema-version.ts +6 -0
@@ -0,0 +1,219 @@
1
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
2
+ import * as path from "node:path";
3
+ import ignore from "ignore";
4
+ const HARD_DENYLIST = [
5
+ ".kb",
6
+ ".git",
7
+ "node_modules",
8
+ "vendor",
9
+ "third_party",
10
+ ".sisyphus",
11
+ ".opencode",
12
+ ];
13
+ function readIgnoreFileLines(filePath) {
14
+ if (!existsSync(filePath))
15
+ return [];
16
+ try {
17
+ const content = readFileSync(filePath, "utf8");
18
+ return content
19
+ .split(/\r?\n/)
20
+ .map((l) => l.trim())
21
+ .filter((l) => l.length > 0 && !l.startsWith("#"));
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ function toPosix(p) {
28
+ return p.split(path.sep).join("/");
29
+ }
30
+ // implements REQ-001
31
+ export function createRepoIgnorePolicy(workspaceRoot) {
32
+ const root = path.resolve(workspaceRoot);
33
+ // Load root .gitignore
34
+ const rootGitignorePath = path.join(root, ".gitignore");
35
+ const rootGitPatterns = readIgnoreFileLines(rootGitignorePath);
36
+ // Load .git/info/exclude
37
+ const gitInfoExcludePath = path.join(root, ".git", "info", "exclude");
38
+ const gitInfoPatterns = readIgnoreFileLines(gitInfoExcludePath);
39
+ // Find nested .gitignore files (skip scanning inside hard denylist directories)
40
+ const nestedPatterns = new Map();
41
+ function walk(dirAbs) {
42
+ let entries;
43
+ try {
44
+ entries = readdirSync(dirAbs, { withFileTypes: true });
45
+ }
46
+ catch {
47
+ return;
48
+ }
49
+ for (const ent of entries) {
50
+ const name = String(ent.name);
51
+ const abs = path.join(dirAbs, name);
52
+ if (ent.isDirectory()) {
53
+ // avoid descending into common heavy or control directories
54
+ if (HARD_DENYLIST.includes(name))
55
+ continue;
56
+ // also avoid .git itself to prevent reading internal excludes as nested
57
+ if (name === ".git")
58
+ continue;
59
+ walk(abs);
60
+ }
61
+ else if (ent.isFile()) {
62
+ if (name === ".gitignore") {
63
+ // skip root .gitignore (we already loaded it)
64
+ if (path.resolve(dirAbs) === root)
65
+ continue;
66
+ const patterns = readIgnoreFileLines(abs);
67
+ const relDir = path.relative(root, dirAbs) || ".";
68
+ nestedPatterns.set(toPosix(relDir), patterns);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ walk(root);
74
+ // Create ignore instances
75
+ const rootIgnore = ignore();
76
+ if (rootGitPatterns.length > 0)
77
+ rootIgnore.add(rootGitPatterns);
78
+ const gitInfoIgnore = ignore();
79
+ if (gitInfoPatterns.length > 0)
80
+ gitInfoIgnore.add(gitInfoPatterns);
81
+ const nestedIgnoreMap = new Map();
82
+ for (const [dirRel, pats] of nestedPatterns.entries()) {
83
+ const ig = ignore();
84
+ if (pats.length > 0)
85
+ ig.add(pats);
86
+ nestedIgnoreMap.set(dirRel, ig);
87
+ }
88
+ // Prepare nested directories sorted by specificity (longest first)
89
+ const nestedDirsSorted = Array.from(nestedIgnoreMap.keys()).sort((a, b) => b.length - a.length);
90
+ function isPathOutsideWorkspace(absPath) {
91
+ const rel = path.relative(root, absPath);
92
+ // path.relative returns paths starting with '..' for outside
93
+ return rel === "" ? false : rel.split(path.sep)[0] === "..";
94
+ }
95
+ function matchesHardDeny(relPosix) {
96
+ const segments = relPosix.split("/").filter(Boolean);
97
+ for (const deny of HARD_DENYLIST) {
98
+ if (segments.includes(deny))
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ function isIgnoredInternal(inputPath) {
104
+ // Resolve to absolute and relative path inside workspace
105
+ const abs = path.isAbsolute(inputPath) ? path.resolve(inputPath) : path.resolve(root, inputPath);
106
+ if (path.isAbsolute(inputPath) && isPathOutsideWorkspace(abs)) {
107
+ return { ignored: true, reason: "outside_workspace" };
108
+ }
109
+ const rel = path.relative(root, abs) || ".";
110
+ const relPosix = toPosix(rel);
111
+ // Hard denylist always wins
112
+ if (matchesHardDeny(relPosix))
113
+ return { ignored: true, reason: "hard_deny" };
114
+ // Root .gitignore
115
+ try {
116
+ if (rootGitPatterns.length > 0 && rootIgnore.ignores(relPosix)) {
117
+ return { ignored: true, reason: "gitignored" };
118
+ }
119
+ }
120
+ catch (e) {
121
+ // ignore errors from library usage; continue
122
+ }
123
+ // .git/info/exclude
124
+ try {
125
+ if (gitInfoPatterns.length > 0 && gitInfoIgnore.ignores(relPosix)) {
126
+ return { ignored: true, reason: "git_info_exclude" };
127
+ }
128
+ }
129
+ catch (e) {
130
+ // noop
131
+ }
132
+ // Nested .gitignore (apply relative to their directory)
133
+ for (const dirRel of nestedDirsSorted) {
134
+ // dirRel is '.' for nested at root which we skipped, so dirRel will be like 'docs'
135
+ if (dirRel === ".")
136
+ continue;
137
+ if (relPosix === dirRel || relPosix.startsWith(dirRel + "/")) {
138
+ const sub = relPosix === dirRel ? "." : relPosix.slice(dirRel.length + 1);
139
+ const ig = nestedIgnoreMap.get(dirRel);
140
+ try {
141
+ if (ig && ig.ignores(sub))
142
+ return { ignored: true, reason: "gitignored" };
143
+ }
144
+ catch (e) {
145
+ // noop
146
+ }
147
+ }
148
+ }
149
+ return { ignored: false };
150
+ }
151
+ function getFastGlobIgnoreGlobs() {
152
+ const globs = [];
153
+ // Hard denylist globs
154
+ for (const d of HARD_DENYLIST) {
155
+ // match directory and its contents anywhere
156
+ globs.push(`**/${d}/**`);
157
+ globs.push(`**/${d}`);
158
+ }
159
+ // Root .gitignore patterns (convert to simple globs)
160
+ for (const p of rootGitPatterns) {
161
+ if (!p || p.startsWith("#") || p.startsWith("!"))
162
+ continue;
163
+ let pat = p;
164
+ if (pat.startsWith("/"))
165
+ pat = pat.slice(1);
166
+ if (pat.includes("/")) {
167
+ // anchored path
168
+ globs.push(`**/${toPosix(pat)}`);
169
+ }
170
+ else {
171
+ globs.push(`**/${pat}`);
172
+ }
173
+ }
174
+ // .git/info/exclude patterns
175
+ for (const p of gitInfoPatterns) {
176
+ if (!p || p.startsWith("#") || p.startsWith("!"))
177
+ continue;
178
+ let pat = p;
179
+ if (pat.startsWith("/"))
180
+ pat = pat.slice(1);
181
+ if (pat.includes("/")) {
182
+ globs.push(`**/${toPosix(pat)}`);
183
+ }
184
+ else {
185
+ globs.push(`**/${pat}`);
186
+ }
187
+ }
188
+ // Nested .gitignore patterns - prefix with directory path
189
+ // Use the raw patterns collected in nestedPatterns so we scope patterns
190
+ // to the nested directory instead of ignoring the entire directory.
191
+ // Debug: print nested patterns and the computed globs to help diagnosing test failures.
192
+ for (const [dirRel, patterns] of nestedPatterns.entries()) {
193
+ for (const p of patterns) {
194
+ if (!p || p.startsWith("#") || p.startsWith("!"))
195
+ continue;
196
+ let pat = p;
197
+ if (pat.startsWith("/"))
198
+ pat = pat.slice(1);
199
+ const prefix = dirRel === "." ? "" : `${dirRel}/`;
200
+ if (pat.includes("/")) {
201
+ globs.push(`**/${prefix}${toPosix(pat)}`);
202
+ }
203
+ else {
204
+ globs.push(`**/${prefix}${pat}`);
205
+ }
206
+ }
207
+ }
208
+ return Array.from(new Set(globs));
209
+ }
210
+ return {
211
+ isIgnored(inputPath) {
212
+ return isIgnoredInternal(inputPath).ignored;
213
+ },
214
+ getFastGlobIgnoreGlobs,
215
+ explain(inputPath) {
216
+ return isIgnoredInternal(inputPath);
217
+ },
218
+ };
219
+ }
@@ -0,0 +1,3 @@
1
+ export type { SchemaVersionStatus } from "../utils/schema-version.js";
2
+ export { LATEST_KB_SCHEMA_VERSION, getSchemaVersionStatus, normalizeSchemaVersion, } from "../utils/schema-version.js";
3
+ //# sourceMappingURL=schema-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-version.d.ts","sourceRoot":"","sources":["../../src/public/schema-version.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1 @@
1
+ export { LATEST_KB_SCHEMA_VERSION, getSchemaVersionStatus, normalizeSchemaVersion, } from "../utils/schema-version.js";
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Stable contract for staged Kibi impact evidence.
3
+ *
4
+ * This module is intentionally explicit and non-heuristic. Upstream staged-file
5
+ * analysis decides whether a source edit is behavior-changing; this contract only
6
+ * records that decision and the staged KB artifacts that cover it.
7
+ */
8
+ /** User-facing KB schema documentation cited by staged diagnostics. */
9
+ export declare const KIBI_ENTITY_SCHEMA_DOC = "docs/entity-schema.md";
10
+ /** Canonical symbols manifest path used by staged traceability enforcement. */
11
+ export declare const KIBI_SYMBOLS_MANIFEST_PATH = "documentation/symbols.yaml";
12
+ /** Canonical symbol coordinates artifact used by staged traceability enforcement. */
13
+ export declare const KIBI_SYMBOL_COORDINATES_PATH = "documentation/symbol-coordinates.yaml";
14
+ /** Explicit declaration string for audited no-impact overrides. */
15
+ export declare const KIBI_NO_IMPACT_DECLARATION = "Kibi-Impact: none";
16
+ /** Canonical Kibi entity types that can provide staged evidence. */
17
+ export type KibiEntityType = "req" | "scenario" | "test" | "adr" | "flag" | "event" | "symbol" | "fact";
18
+ /**
19
+ * Explicit source-edit classification from upstream staged analysis.
20
+ *
21
+ * - `behavior_source_edit`: a supported staged source change already classified
22
+ * as behavior-changing or traceability-relevant by the caller.
23
+ * - `non_behavior_source_edit`: a supported staged source change that remains in
24
+ * scope for auditing but does not require KB changes.
25
+ */
26
+ export type SourceChangeKind = "behavior_source_edit" | "non_behavior_source_edit";
27
+ /** One staged source file participating in Kibi impact evaluation. */
28
+ export interface KibiImpactSourceChange {
29
+ /** Repo-relative staged source path. */
30
+ path: string;
31
+ /** Explicit upstream classification; this module does not infer it. */
32
+ kind: SourceChangeKind;
33
+ }
34
+ /**
35
+ * Staged KB markdown evidence linked to one or more staged source files.
36
+ *
37
+ * Evidence is explicit only when the staged artifact names concrete KB entities
38
+ * and lists the staged source paths it is intended to cover.
39
+ */
40
+ export interface KibiImpactKbArtifact {
41
+ /** Artifact category. Kept narrow to avoid heuristic interpretation. */
42
+ kind: "entity_markdown" | "symbols_manifest";
43
+ /** Repo-relative staged KB artifact path. */
44
+ path: string;
45
+ /** Canonical Kibi entity types present in the staged artifact. */
46
+ entityTypes: KibiEntityType[];
47
+ /** Concrete KB entities updated by the staged artifact. */
48
+ entityIds: string[];
49
+ /** Repo-relative source paths explicitly covered by this artifact. */
50
+ sourcePaths: string[];
51
+ }
52
+ /**
53
+ * Deterministic symbol coordinates artifact state for the staged change-set.
54
+ *
55
+ * - `not_required`: symbol extraction output did not change for the listed
56
+ * staged source paths.
57
+ * - `fresh`: a staged coordinate refresh covers the listed source paths.
58
+ * - `stale`: coordinate artifact content is reverted, outdated, or otherwise does not match
59
+ * the staged symbol extraction result.
60
+ * - `missing`: a refresh is required but no staged coordinate artifact exists.
61
+ */
62
+ export interface KibiImpactSymbolsManifest {
63
+ /** Canonical repo-relative symbol coordinate artifact path. */
64
+ path: string;
65
+ /** Explicit manifest freshness state. */
66
+ state: "not_required" | "fresh" | "stale" | "missing";
67
+ /** Repo-relative staged source paths whose symbol output this state describes. */
68
+ sourcePaths: string[];
69
+ }
70
+ /** Supported audited reasons for a no-impact override. */
71
+ export type KibiNoImpactReason = "false_positive" | "non_behavioral_source_edit";
72
+ /**
73
+ * Explicit no-impact override record.
74
+ *
75
+ * Overrides are only valid for non-behavioral edits or classifier false
76
+ * positives. They never satisfy real behavior-changing source edits.
77
+ */
78
+ export interface KibiNoImpactOverride {
79
+ /** Required literal declaration. */
80
+ declaration: typeof KIBI_NO_IMPACT_DECLARATION;
81
+ /** Repo-relative staged record path carrying the override. */
82
+ path: string;
83
+ /** Repo-relative source paths covered by the override record. */
84
+ sourcePaths: string[];
85
+ /** Audited reason for allowing the override. */
86
+ reason: KibiNoImpactReason;
87
+ /** Human-readable justification stored with the override record. */
88
+ rationale: string;
89
+ }
90
+ /** Explicit evidence state: staged KB artifacts are present. */
91
+ export interface KibiImpactKbChangesMode {
92
+ kind: "kb_changes";
93
+ /** Staged KB markdown artifacts linked to staged source changes. */
94
+ kbArtifacts: KibiImpactKbArtifact[];
95
+ }
96
+ /** Explicit evidence state: a staged no-impact override is being used. */
97
+ export interface KibiImpactNoImpactOverrideMode {
98
+ kind: "no_impact_override";
99
+ /** Staged override record for false positives or non-behavioral edits. */
100
+ override: KibiNoImpactOverride;
101
+ }
102
+ /** Explicit evidence state: no staged KB evidence or override exists. */
103
+ export interface KibiImpactMissingMode {
104
+ kind: "missing";
105
+ }
106
+ /** Discriminated union describing how the staged change-set is justified. */
107
+ export type KibiImpactMode = KibiImpactKbChangesMode | KibiImpactNoImpactOverrideMode | KibiImpactMissingMode;
108
+ /**
109
+ * Full evidence snapshot for staged Kibi impact enforcement.
110
+ *
111
+ * `sourceChanges` is always required so diagnostics can cite exact staged source
112
+ * files. `mode` captures whether those files are backed by KB changes, by an
113
+ * audited no-impact override, or by nothing. `symbolsManifest` records whether a
114
+ * staged manifest refresh is part of that evidence.
115
+ */
116
+ export interface KibiImpactEvidence {
117
+ /** All staged source files in scope for Kibi impact enforcement. */
118
+ sourceChanges: KibiImpactSourceChange[];
119
+ /** Deterministic staged symbols manifest state for the same change-set. */
120
+ symbolsManifest: KibiImpactSymbolsManifest;
121
+ /** Explicit evidence mode for the staged change-set. */
122
+ mode: KibiImpactMode;
123
+ }
124
+ /** Returns staged behavior-changing source paths only. */
125
+ export declare function getBehaviorSourcePaths(evidence: KibiImpactEvidence): string[];
126
+ /** Returns staged source paths covered by explicit KB artifacts. */
127
+ export declare function getKbCoveredSourcePaths(evidence: KibiImpactEvidence): string[];
128
+ /** Returns staged source paths covered by a fresh symbol coordinate refresh. */
129
+ export declare function getFreshSymbolsManifestSourcePaths(evidence: KibiImpactEvidence): string[];
130
+ /**
131
+ * Returns all staged evidence file paths that a later CLI integration can cite
132
+ * in diagnostics or logs.
133
+ */
134
+ export declare function getKbEvidencePaths(evidence: KibiImpactEvidence): string[];
135
+ /** True when a staged no-impact override includes a non-empty rationale. */
136
+ export declare function hasOverrideRationale(evidence: KibiImpactEvidence): boolean;
137
+ /**
138
+ * Returns behavior-changing staged source files that still lack valid Kibi
139
+ * impact evidence.
140
+ */
141
+ export declare function getMissingBehaviorSourcePaths(evidence: KibiImpactEvidence): string[];
142
+ //# sourceMappingURL=evidence-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-model.d.ts","sourceRoot":"","sources":["../../src/traceability/evidence-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Stable contract for staged Kibi impact evidence.
3
+ *
4
+ * This module is intentionally explicit and non-heuristic. Upstream staged-file
5
+ * analysis decides whether a source edit is behavior-changing; this contract only
6
+ * records that decision and the staged KB artifacts that cover it.
7
+ */
8
+ /** User-facing KB schema documentation cited by staged diagnostics. */
9
+ export const KIBI_ENTITY_SCHEMA_DOC = "docs/entity-schema.md";
10
+ /** Canonical symbols manifest path used by staged traceability enforcement. */
11
+ export const KIBI_SYMBOLS_MANIFEST_PATH = "documentation/symbols.yaml";
12
+ /** Canonical symbol coordinates artifact used by staged traceability enforcement. */
13
+ export const KIBI_SYMBOL_COORDINATES_PATH = "documentation/symbol-coordinates.yaml";
14
+ /** Explicit declaration string for audited no-impact overrides. */
15
+ export const KIBI_NO_IMPACT_DECLARATION = "Kibi-Impact: none";
16
+ function uniqueSorted(values) {
17
+ return Array.from(new Set(values)).sort();
18
+ }
19
+ /** Returns staged behavior-changing source paths only. */
20
+ export function getBehaviorSourcePaths(evidence) {
21
+ return evidence.sourceChanges
22
+ .filter((change) => change.kind === "behavior_source_edit")
23
+ .map((change) => change.path)
24
+ .sort();
25
+ }
26
+ /** Returns staged source paths covered by explicit KB artifacts. */
27
+ export function getKbCoveredSourcePaths(evidence) {
28
+ if (evidence.mode.kind !== "kb_changes") {
29
+ return [];
30
+ }
31
+ return uniqueSorted(evidence.mode.kbArtifacts.flatMap((artifact) => artifact.sourcePaths));
32
+ }
33
+ /** Returns staged source paths covered by a fresh symbol coordinate refresh. */
34
+ export function getFreshSymbolsManifestSourcePaths(evidence) {
35
+ if (evidence.symbolsManifest.state !== "fresh") {
36
+ return [];
37
+ }
38
+ return [...evidence.symbolsManifest.sourcePaths].sort();
39
+ }
40
+ /**
41
+ * Returns all staged evidence file paths that a later CLI integration can cite
42
+ * in diagnostics or logs.
43
+ */
44
+ export function getKbEvidencePaths(evidence) {
45
+ const paths = [];
46
+ if (evidence.mode.kind === "kb_changes") {
47
+ paths.push(...evidence.mode.kbArtifacts.map((artifact) => artifact.path));
48
+ }
49
+ if (evidence.symbolsManifest.state === "fresh") {
50
+ paths.push(evidence.symbolsManifest.path);
51
+ }
52
+ return uniqueSorted(paths);
53
+ }
54
+ /** True when a staged no-impact override includes a non-empty rationale. */
55
+ export function hasOverrideRationale(evidence) {
56
+ return (evidence.mode.kind === "no_impact_override" &&
57
+ evidence.mode.override.rationale.trim().length > 0);
58
+ }
59
+ /**
60
+ * Returns behavior-changing staged source files that still lack valid Kibi
61
+ * impact evidence.
62
+ */
63
+ export function getMissingBehaviorSourcePaths(evidence) {
64
+ const behaviorPaths = getBehaviorSourcePaths(evidence);
65
+ const coveredPaths = new Set([
66
+ ...getKbCoveredSourcePaths(evidence),
67
+ ...getFreshSymbolsManifestSourcePaths(evidence),
68
+ ]);
69
+ return behaviorPaths.filter((path) => !coveredPaths.has(path));
70
+ }
@@ -8,6 +8,7 @@ export interface StagedFile {
8
8
  status: Status;
9
9
  oldPath?: string;
10
10
  hunkRanges: HunkRange[];
11
+ diffText?: string;
11
12
  content?: string;
12
13
  }
13
14
  type ExecFn = (cmd: string, opts: {
@@ -1 +1 @@
1
- {"version":3,"file":"git-staged.d.ts","sourceRoot":"","sources":["../../src/traceability/git-staged.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAAC;AAalE;;GAEG;AAEH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAwB5C;AAiDD;;GAEG;AACH,wBAAgB,kBAAkB,CAEhC,QAAQ,EAAE,MAAM,EAChB,SAAS,UAAQ,GAChB,SAAS,EAAE,CAuBb;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAiB,GAAG,UAAU,EAAE,CAuGpE;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"git-staged.d.ts","sourceRoot":"","sources":["../../src/traceability/git-staged.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAAC;AAalE;;GAEG;AAEH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAwB5C;AAsED;;GAEG;AACH,wBAAgB,kBAAkB,CAEhC,QAAQ,EAAE,MAAM,EAChB,SAAS,UAAQ,GAChB,SAAS,EAAE,CAuBb;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAiB,GAAG,UAAU,EAAE,CAwGpE;AAED,eAAe,cAAc,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { isCliTraceOrDebugEnabled } from "../env.js";
3
+ import { loadConfig } from "../utils/config.js";
3
4
  function runGit(cmd, exec) {
4
5
  try {
5
6
  return exec(cmd, { encoding: "utf8" });
@@ -47,8 +48,20 @@ const SUPPORTED_EXT = new Set([
47
48
  ".mjs",
48
49
  ".cjs",
49
50
  ]);
50
- const SUPPORTED_MANIFEST = new Set(["symbols.yaml", "symbols.yml"]);
51
- const ENTITY_MARKDOWN_DIRS = ["/requirements/", "/scenarios/", "/tests/"];
51
+ const SUPPORTED_MANIFEST = new Set([
52
+ "symbols.yaml",
53
+ "symbols.yml",
54
+ "symbol-coordinates.yaml",
55
+ ]);
56
+ const ENTITY_MARKDOWN_DIRS = [
57
+ "/requirements/",
58
+ "/scenarios/",
59
+ "/tests/",
60
+ "/facts/",
61
+ "/adr/",
62
+ "/flags/",
63
+ "/events/",
64
+ ];
52
65
  function shouldLogTraceDebug() {
53
66
  return isCliTraceOrDebugEnabled();
54
67
  }
@@ -72,13 +85,24 @@ function isEntityMarkdown(p) {
72
85
  return false;
73
86
  }
74
87
  function isManifestFile(p) {
75
- const base = p.split(/[\/]/).pop();
88
+ const base = p.split(/[\\/]/).pop();
76
89
  if (!base)
77
90
  return false;
78
91
  for (const name of SUPPORTED_MANIFEST) {
79
92
  if (base === name)
80
93
  return true;
81
94
  }
95
+ try {
96
+ const config = loadConfig(process.cwd());
97
+ if (config.paths.symbols) {
98
+ const configuredBase = config.paths.symbols.split(/[\\/]/).pop();
99
+ if (configuredBase && base === configuredBase)
100
+ return true;
101
+ }
102
+ }
103
+ catch {
104
+ // ignore config read errors
105
+ }
82
106
  return false;
83
107
  }
84
108
  /**
@@ -198,6 +222,7 @@ export function getStagedFiles(exec = execSync) {
198
222
  status,
199
223
  ...(oldPath !== undefined ? { oldPath } : {}),
200
224
  hunkRanges,
225
+ diffText,
201
226
  content,
202
227
  });
203
228
  }
@@ -0,0 +1,25 @@
1
+ import { type KibiImpactEvidence } from "./evidence-model.js";
2
+ export type KibiImpactDiagnosticId = "kibi_impact_evidence_missing" | "symbols_manifest_stale" | "kibi_impact_override_missing_rationale";
3
+ export interface KibiImpactDiagnostic {
4
+ /** Stable staged-enforcement diagnostic identifier. */
5
+ id: KibiImpactDiagnosticId;
6
+ /** Hard-gate severity for staged enforcement. */
7
+ severity: "error";
8
+ /** Repo-relative files that explain why the diagnostic fired. */
9
+ files: string[];
10
+ /** User-facing docs that explain the policy. */
11
+ docs: string[];
12
+ /** Exact CLI-facing diagnostic message. */
13
+ message: string;
14
+ /** Deterministic remediation guidance. */
15
+ suggestion: string;
16
+ }
17
+ /**
18
+ * Collects deterministic staged diagnostics for Kibi impact enforcement.
19
+ *
20
+ * This function assumes upstream staged analysis has already classified source
21
+ * files and manifest freshness. It only evaluates explicit predicates recorded in
22
+ * `KibiImpactEvidence`.
23
+ */
24
+ export declare function collectStagedKibiDiagnostics(evidence: KibiImpactEvidence): KibiImpactDiagnostic[];
25
+ //# sourceMappingURL=staged-diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staged-diagnostics.d.ts","sourceRoot":"","sources":["../../src/traceability/staged-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,sBAAsB,GAC9B,8BAA8B,GAC9B,wBAAwB,GACxB,wCAAwC,CAAC;AAE7C,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,EAAE,EAAE,sBAAsB,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gDAAgD;IAChD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;CACpB;AAsDD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,kBAAkB,GAC3B,oBAAoB,EAAE,CAoCxB"}
@@ -0,0 +1,67 @@
1
+ import { KIBI_ENTITY_SCHEMA_DOC, KIBI_SYMBOL_COORDINATES_PATH, KIBI_SYMBOLS_MANIFEST_PATH, getBehaviorSourcePaths, getMissingBehaviorSourcePaths, hasOverrideRationale, } from "./evidence-model.js";
2
+ function formatFileList(paths) {
3
+ return paths.join(", ");
4
+ }
5
+ function createMissingEvidenceDiagnostic(paths) {
6
+ return {
7
+ id: "kibi_impact_evidence_missing",
8
+ severity: "error",
9
+ files: [...paths],
10
+ docs: [KIBI_ENTITY_SCHEMA_DOC],
11
+ message: `Behavior-changing staged files are missing Kibi impact evidence (see ${KIBI_ENTITY_SCHEMA_DOC}): ${formatFileList(paths)}`,
12
+ suggestion: `Query Kibi via MCP before deciding, then stage requirement/scenario/test/fact/symbol markdown evidence, staged authored ${KIBI_SYMBOLS_MANIFEST_PATH} metadata, or refreshed ${KIBI_SYMBOL_COORDINATES_PATH}. Re-run kibi check --staged after staging the evidence.`,
13
+ };
14
+ }
15
+ function createSymbolsManifestStaleDiagnostic(paths) {
16
+ return {
17
+ id: "symbols_manifest_stale",
18
+ severity: "error",
19
+ files: [KIBI_SYMBOL_COORDINATES_PATH, ...paths],
20
+ docs: [KIBI_ENTITY_SCHEMA_DOC],
21
+ message: `${KIBI_SYMBOL_COORDINATES_PATH} is stale or missing for staged source files: ${formatFileList(paths)}`,
22
+ suggestion: `Run kibi sync --refresh-symbol-coordinates && git add ${KIBI_SYMBOL_COORDINATES_PATH} ${KIBI_SYMBOLS_MANIFEST_PATH}, then re-run kibi check --staged.`,
23
+ };
24
+ }
25
+ function createMissingOverrideRationaleDiagnostic(evidence) {
26
+ if (evidence.mode.kind !== "no_impact_override") {
27
+ throw new Error("Override rationale diagnostic requires a no-impact override");
28
+ }
29
+ const paths = [...evidence.mode.override.sourcePaths].sort();
30
+ return {
31
+ id: "kibi_impact_override_missing_rationale",
32
+ severity: "error",
33
+ files: [evidence.mode.override.path, ...paths],
34
+ docs: [KIBI_ENTITY_SCHEMA_DOC],
35
+ message: `Kibi-Impact: none override is missing rationale for staged source files: ${formatFileList(paths)}`,
36
+ suggestion: "Add a non-empty rationale in the same staged override record, keep overrides limited to false positives or non-behavioral source edits, and re-run kibi check --staged.",
37
+ };
38
+ }
39
+ /**
40
+ * Collects deterministic staged diagnostics for Kibi impact enforcement.
41
+ *
42
+ * This function assumes upstream staged analysis has already classified source
43
+ * files and manifest freshness. It only evaluates explicit predicates recorded in
44
+ * `KibiImpactEvidence`.
45
+ */
46
+ export function collectStagedKibiDiagnostics(evidence) {
47
+ const diagnostics = [];
48
+ if (evidence.mode.kind === "no_impact_override" &&
49
+ !hasOverrideRationale(evidence)) {
50
+ diagnostics.push(createMissingOverrideRationaleDiagnostic(evidence));
51
+ }
52
+ if ((evidence.symbolsManifest.state === "stale" ||
53
+ evidence.symbolsManifest.state === "missing") &&
54
+ evidence.symbolsManifest.sourcePaths.length > 0) {
55
+ diagnostics.push(createSymbolsManifestStaleDiagnostic([...evidence.symbolsManifest.sourcePaths].sort()));
56
+ }
57
+ const missingBehaviorPaths = getMissingBehaviorSourcePaths(evidence);
58
+ if (missingBehaviorPaths.length > 0) {
59
+ diagnostics.push(createMissingEvidenceDiagnostic(missingBehaviorPaths));
60
+ }
61
+ if (evidence.mode.kind === "no_impact_override" &&
62
+ evidence.mode.override.sourcePaths.length === 0 &&
63
+ getBehaviorSourcePaths(evidence).length === 0) {
64
+ return diagnostics;
65
+ }
66
+ return diagnostics;
67
+ }