@nimiplatform/nimi-coding 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/adapters/README.md +25 -0
- package/adapters/claude/README.md +89 -0
- package/adapters/claude/profile.yaml +70 -0
- package/adapters/codex/README.md +53 -0
- package/adapters/codex/profile.yaml +78 -0
- package/adapters/oh-my-codex/README.md +185 -0
- package/adapters/oh-my-codex/profile.yaml +46 -0
- package/bin/nimicoding.mjs +6 -0
- package/cli/commands/admit-high-risk-decision.mjs +108 -0
- package/cli/commands/audit-sweep.mjs +341 -0
- package/cli/commands/blueprint-audit.mjs +91 -0
- package/cli/commands/clear.mjs +168 -0
- package/cli/commands/closeout.mjs +183 -0
- package/cli/commands/decide-high-risk-execution.mjs +124 -0
- package/cli/commands/doctor.mjs +53 -0
- package/cli/commands/generate-spec-derived-docs.mjs +131 -0
- package/cli/commands/handoff.mjs +123 -0
- package/cli/commands/ingest-high-risk-execution.mjs +95 -0
- package/cli/commands/review-high-risk-execution.mjs +95 -0
- package/cli/commands/start.mjs +717 -0
- package/cli/commands/topic-formatters.mjs +382 -0
- package/cli/commands/topic-goal.mjs +33 -0
- package/cli/commands/topic-options-shared.mjs +27 -0
- package/cli/commands/topic-options-workflow.mjs +767 -0
- package/cli/commands/topic-options.mjs +626 -0
- package/cli/commands/topic-runner.mjs +169 -0
- package/cli/commands/topic.mjs +795 -0
- package/cli/commands/validate-acceptance.mjs +5 -0
- package/cli/commands/validate-ai-governance.mjs +214 -0
- package/cli/commands/validate-execution-packet.mjs +5 -0
- package/cli/commands/validate-orchestration-state.mjs +5 -0
- package/cli/commands/validate-prompt.mjs +5 -0
- package/cli/commands/validate-spec-audit.mjs +27 -0
- package/cli/commands/validate-spec-governance.mjs +124 -0
- package/cli/commands/validate-spec-tree.mjs +27 -0
- package/cli/commands/validate-worker-output.mjs +5 -0
- package/cli/constants.mjs +489 -0
- package/cli/help.mjs +134 -0
- package/cli/index.mjs +103 -0
- package/cli/lib/adapter-profiles.mjs +403 -0
- package/cli/lib/audit-execution.mjs +52 -0
- package/cli/lib/audit-sweep-runtime/admissions.mjs +381 -0
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +333 -0
- package/cli/lib/audit-sweep-runtime/chunks.mjs +697 -0
- package/cli/lib/audit-sweep-runtime/closeout.mjs +144 -0
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +639 -0
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +515 -0
- package/cli/lib/audit-sweep-runtime/common.mjs +329 -0
- package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +172 -0
- package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +152 -0
- package/cli/lib/audit-sweep-runtime/format.mjs +57 -0
- package/cli/lib/audit-sweep-runtime/ingest.mjs +486 -0
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +198 -0
- package/cli/lib/audit-sweep-runtime/inventory.mjs +728 -0
- package/cli/lib/audit-sweep-runtime/ledger.mjs +315 -0
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +101 -0
- package/cli/lib/audit-sweep-runtime/remediation.mjs +349 -0
- package/cli/lib/audit-sweep-runtime/rerun.mjs +129 -0
- package/cli/lib/audit-sweep-runtime/risk-budget.mjs +300 -0
- package/cli/lib/audit-sweep-runtime/status.mjs +62 -0
- package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +215 -0
- package/cli/lib/audit-sweep-runtime/validators.mjs +758 -0
- package/cli/lib/audit-sweep.mjs +18 -0
- package/cli/lib/authority-convergence.mjs +309 -0
- package/cli/lib/blueprint-audit.mjs +370 -0
- package/cli/lib/bootstrap.mjs +228 -0
- package/cli/lib/closeout.mjs +623 -0
- package/cli/lib/codex-sdk-runner.mjs +76 -0
- package/cli/lib/contracts.mjs +180 -0
- package/cli/lib/doctor.mjs +18 -0
- package/cli/lib/entrypoints.mjs +274 -0
- package/cli/lib/external-execution.mjs +101 -0
- package/cli/lib/fs-helpers.mjs +33 -0
- package/cli/lib/handoff.mjs +785 -0
- package/cli/lib/high-risk-admission.mjs +442 -0
- package/cli/lib/high-risk-decision.mjs +324 -0
- package/cli/lib/high-risk-ingest.mjs +317 -0
- package/cli/lib/high-risk-review.mjs +263 -0
- package/cli/lib/internal/contracts-loaders.mjs +132 -0
- package/cli/lib/internal/contracts-parse-high-risk.mjs +131 -0
- package/cli/lib/internal/contracts-parse.mjs +457 -0
- package/cli/lib/internal/contracts-validators.mjs +398 -0
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +359 -0
- package/cli/lib/internal/doctor-delegated-surface.mjs +256 -0
- package/cli/lib/internal/doctor-finalize.mjs +385 -0
- package/cli/lib/internal/doctor-format.mjs +286 -0
- package/cli/lib/internal/doctor-inspectors.mjs +294 -0
- package/cli/lib/internal/doctor-state.mjs +205 -0
- package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +315 -0
- package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +358 -0
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +155 -0
- package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +173 -0
- package/cli/lib/internal/governance/config.mjs +150 -0
- package/cli/lib/internal/governance/runner.mjs +35 -0
- package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +49 -0
- package/cli/lib/internal/validators-artifacts.mjs +515 -0
- package/cli/lib/internal/validators-shared.mjs +28 -0
- package/cli/lib/internal/validators-spec-helpers.mjs +186 -0
- package/cli/lib/internal/validators-spec.mjs +410 -0
- package/cli/lib/shared.mjs +83 -0
- package/cli/lib/topic-draft-packets.mjs +48 -0
- package/cli/lib/topic-goal.mjs +361 -0
- package/cli/lib/topic-runner.mjs +772 -0
- package/cli/lib/topic.mjs +93 -0
- package/cli/lib/ui.mjs +178 -0
- package/cli/lib/validators.mjs +78 -0
- package/cli/lib/value-helpers.mjs +24 -0
- package/cli/lib/yaml-helpers.mjs +133 -0
- package/cli/nimicoding.mjs +1 -0
- package/cli/seeds/bootstrap.mjs +47 -0
- package/config/audit-execution-artifacts.yaml +20 -0
- package/config/bootstrap.yaml +6 -0
- package/config/external-execution-artifacts.yaml +16 -0
- package/config/host-adapter.yaml +30 -0
- package/config/host-profile.yaml +29 -0
- package/config/installer-evidence.yaml +31 -0
- package/config/skill-installer.yaml +23 -0
- package/config/skill-manifest.yaml +46 -0
- package/config/skills.yaml +30 -0
- package/config/spec-generation-inputs.yaml +25 -0
- package/contracts/acceptance.schema.yaml +16 -0
- package/contracts/admission-checklist.schema.yaml +15 -0
- package/contracts/audit-chunk.schema.yaml +110 -0
- package/contracts/audit-closeout.schema.yaml +51 -0
- package/contracts/audit-finding.schema.yaml +61 -0
- package/contracts/audit-ledger.schema.yaml +138 -0
- package/contracts/audit-plan.schema.yaml +123 -0
- package/contracts/audit-remediation-map.schema.yaml +51 -0
- package/contracts/audit-rerun.schema.yaml +31 -0
- package/contracts/audit-sweep-result.yaml +49 -0
- package/contracts/authority-convergence-audit.schema.yaml +19 -0
- package/contracts/closeout.schema.yaml +25 -0
- package/contracts/decision-review.schema.yaml +16 -0
- package/contracts/doc-spec-audit-result.yaml +19 -0
- package/contracts/execution-packet.schema.yaml +49 -0
- package/contracts/external-host-compatibility.yaml +22 -0
- package/contracts/forbidden-shortcuts.catalog.yaml +23 -0
- package/contracts/high-risk-admission.schema.yaml +23 -0
- package/contracts/high-risk-execution-result.yaml +20 -0
- package/contracts/orchestration-state.schema.yaml +41 -0
- package/contracts/overflow-continuation.schema.yaml +12 -0
- package/contracts/packet.schema.yaml +30 -0
- package/contracts/pending-note.schema.yaml +17 -0
- package/contracts/prompt.schema.yaml +12 -0
- package/contracts/remediation.schema.yaml +16 -0
- package/contracts/result.schema.yaml +24 -0
- package/contracts/spec-generation-audit.schema.yaml +31 -0
- package/contracts/spec-generation-inputs.schema.yaml +39 -0
- package/contracts/spec-reconstruction-result.yaml +37 -0
- package/contracts/topic-goal.schema.yaml +78 -0
- package/contracts/topic-run-ledger.schema.yaml +72 -0
- package/contracts/topic-step-decision.schema.yaml +45 -0
- package/contracts/topic.schema.yaml +65 -0
- package/contracts/true-close.schema.yaml +15 -0
- package/contracts/wave.schema.yaml +29 -0
- package/contracts/worker-output.schema.yaml +15 -0
- package/methodology/audit-sweep-p0p1-recall.yaml +45 -0
- package/methodology/authority-convergence-policy.yaml +42 -0
- package/methodology/core.yaml +25 -0
- package/methodology/four-closure-policy.yaml +28 -0
- package/methodology/overflow-continuation-policy.yaml +14 -0
- package/methodology/role-separation-policy.yaml +28 -0
- package/methodology/skill-exchange-projection.yaml +114 -0
- package/methodology/skill-handoff.yaml +34 -0
- package/methodology/skill-installer-result.yaml +27 -0
- package/methodology/skill-installer-summary-projection.yaml +181 -0
- package/methodology/skill-runtime.yaml +23 -0
- package/methodology/spec-reconstruction.yaml +63 -0
- package/methodology/spec-target-truth-profile.yaml +53 -0
- package/methodology/topic-lifecycle-report.yaml +144 -0
- package/methodology/topic-lifecycle.yaml +37 -0
- package/methodology/topic-naming-ontology.yaml +21 -0
- package/methodology/topic-ontology.yaml +38 -0
- package/methodology/topic-validation-policy.yaml +9 -0
- package/methodology/wave-dag-policy.yaml +14 -0
- package/package.json +50 -0
- package/spec/_meta/command-gating-matrix.yaml +110 -0
- package/spec/_meta/generate-drift-migration-checklist.yaml +155 -0
- package/spec/_meta/governance-routing-cutover-checklist.yaml +35 -0
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +44 -0
- package/spec/_meta/spec-authority-cutover-readiness.yaml +104 -0
- package/spec/_meta/spec-tree-model.yaml +72 -0
- package/spec/bootstrap-state.yaml +99 -0
- package/spec/product-scope.yaml +56 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SPEC_GENERATION_AUDIT_REF,
|
|
3
|
+
STANDALONE_COMPLETION_STATUS,
|
|
4
|
+
} from "../../constants.mjs";
|
|
5
|
+
|
|
6
|
+
export function buildCheck(id, ok, detail, severity = ok ? "ok" : "error") {
|
|
7
|
+
return { id, ok, detail, severity };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function emptyDelegatedContracts() {
|
|
11
|
+
return {
|
|
12
|
+
runtimeOwner: null,
|
|
13
|
+
executionMode: null,
|
|
14
|
+
installerMode: null,
|
|
15
|
+
selfHostedRuntime: false,
|
|
16
|
+
triggerMode: null,
|
|
17
|
+
expectedSkillIds: [],
|
|
18
|
+
selectedAdapterId: null,
|
|
19
|
+
admittedAdapterIds: [],
|
|
20
|
+
adapterHandoffMode: null,
|
|
21
|
+
semanticReviewOwner: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function emptyHandoffReadiness() {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
requiredContextOrder: [],
|
|
29
|
+
missingContextEntries: [],
|
|
30
|
+
missingPaths: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function emptyAdapterProfiles() {
|
|
35
|
+
return {
|
|
36
|
+
admitted: [],
|
|
37
|
+
invalid: [],
|
|
38
|
+
selected: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function emptyAuditArtifact() {
|
|
43
|
+
return {
|
|
44
|
+
present: false,
|
|
45
|
+
ok: true,
|
|
46
|
+
artifactPath: ".nimi/local/handoff-results/doc_spec_audit.json",
|
|
47
|
+
outcome: null,
|
|
48
|
+
summaryStatus: null,
|
|
49
|
+
verifiedAt: null,
|
|
50
|
+
reason: "No local doc_spec_audit closeout artifact detected",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function emptySpecGenerationAudit() {
|
|
55
|
+
return {
|
|
56
|
+
present: false,
|
|
57
|
+
ok: false,
|
|
58
|
+
auditPath: SPEC_GENERATION_AUDIT_REF,
|
|
59
|
+
validator: "validate-spec-audit",
|
|
60
|
+
summary: null,
|
|
61
|
+
reason: "No spec generation audit detected yet; it will be required before completed reconstruction closeout",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function emptyCompletionPosture() {
|
|
66
|
+
return {
|
|
67
|
+
completionProfile: null,
|
|
68
|
+
completionStatus: STANDALONE_COMPLETION_STATUS.INCOMPLETE,
|
|
69
|
+
completedSurfaces: [],
|
|
70
|
+
deferredExecutionSurfaces: [],
|
|
71
|
+
promotedParityGapSummary: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function emptyCanonicalTree() {
|
|
76
|
+
return {
|
|
77
|
+
profile: null,
|
|
78
|
+
canonicalRoot: null,
|
|
79
|
+
requiredFiles: [],
|
|
80
|
+
present: [],
|
|
81
|
+
missing: [],
|
|
82
|
+
invalid: [],
|
|
83
|
+
requiredFilesValid: false,
|
|
84
|
+
ready: false,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function emptyLifecycleState() {
|
|
89
|
+
return {
|
|
90
|
+
mode: null,
|
|
91
|
+
treeState: null,
|
|
92
|
+
authorityMode: null,
|
|
93
|
+
blueprintMode: null,
|
|
94
|
+
reconstructionRequired: false,
|
|
95
|
+
readyForAiReconstruction: false,
|
|
96
|
+
cutoverReadiness: {},
|
|
97
|
+
activeAuthorityRoot: null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function emptyCommandGating() {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
entries: [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function emptyBlueprintReference() {
|
|
109
|
+
return {
|
|
110
|
+
present: false,
|
|
111
|
+
ok: true,
|
|
112
|
+
mode: null,
|
|
113
|
+
root: null,
|
|
114
|
+
canonicalTargetRoot: null,
|
|
115
|
+
equivalenceContractRef: null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function emptySpecGenerationInputs() {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
mode: null,
|
|
123
|
+
canonicalTargetRoot: null,
|
|
124
|
+
codeRoots: [],
|
|
125
|
+
docsRoots: [],
|
|
126
|
+
structureRoots: [],
|
|
127
|
+
humanNotePaths: [],
|
|
128
|
+
benchmarkBlueprintRoot: null,
|
|
129
|
+
benchmarkMode: null,
|
|
130
|
+
acceptanceMode: null,
|
|
131
|
+
generationOrder: [],
|
|
132
|
+
inferenceRules: [],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function emptyBenchmarkAuditReadiness() {
|
|
137
|
+
return {
|
|
138
|
+
available: false,
|
|
139
|
+
ready: false,
|
|
140
|
+
benchmarkRoot: null,
|
|
141
|
+
acceptanceMode: null,
|
|
142
|
+
reason: "No benchmark blueprint is declared for this project.",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function emptyExecutionContracts() {
|
|
147
|
+
return {
|
|
148
|
+
total: 0,
|
|
149
|
+
valid: 0,
|
|
150
|
+
invalid: [],
|
|
151
|
+
contracts: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function emptyHostCompatibility() {
|
|
156
|
+
return {
|
|
157
|
+
contractRef: "unknown",
|
|
158
|
+
supportedHostPosture: [],
|
|
159
|
+
supportedHostExamples: [],
|
|
160
|
+
requiredBehavior: [],
|
|
161
|
+
forbiddenBehavior: [],
|
|
162
|
+
genericExternalHostCompatible: false,
|
|
163
|
+
namedOverlaySupport: {
|
|
164
|
+
mode: "generic_only",
|
|
165
|
+
admittedOverlayIds: [],
|
|
166
|
+
selectedOverlayId: null,
|
|
167
|
+
selectedOverlayProfileRef: null,
|
|
168
|
+
selectedOverlayHostClass: null,
|
|
169
|
+
},
|
|
170
|
+
futureOnlyHostSurfaces: [],
|
|
171
|
+
nativeReviewSurfaces: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createDoctorMissingRootResult(projectRoot, detail, nextStep) {
|
|
176
|
+
return {
|
|
177
|
+
projectRoot,
|
|
178
|
+
ok: false,
|
|
179
|
+
bootstrapPresent: false,
|
|
180
|
+
reconstructionRequired: false,
|
|
181
|
+
runtimeInstalled: false,
|
|
182
|
+
bootstrapContract: {
|
|
183
|
+
status: "missing",
|
|
184
|
+
id: null,
|
|
185
|
+
version: null,
|
|
186
|
+
},
|
|
187
|
+
lifecycleState: emptyLifecycleState(),
|
|
188
|
+
specTreeModel: null,
|
|
189
|
+
specGenerationInputs: emptySpecGenerationInputs(),
|
|
190
|
+
canonicalTree: emptyCanonicalTree(),
|
|
191
|
+
specGenerationAudit: emptySpecGenerationAudit(),
|
|
192
|
+
commandGating: emptyCommandGating(),
|
|
193
|
+
blueprintReference: emptyBlueprintReference(),
|
|
194
|
+
benchmarkAuditReadiness: emptyBenchmarkAuditReadiness(),
|
|
195
|
+
delegatedContracts: emptyDelegatedContracts(),
|
|
196
|
+
adapterProfiles: emptyAdapterProfiles(),
|
|
197
|
+
hostCompatibility: emptyHostCompatibility(),
|
|
198
|
+
...emptyCompletionPosture(),
|
|
199
|
+
handoffReadiness: emptyHandoffReadiness(),
|
|
200
|
+
checks: [buildCheck("nimi_root", false, detail)],
|
|
201
|
+
auditArtifact: emptyAuditArtifact(),
|
|
202
|
+
executionContracts: emptyExecutionContracts(),
|
|
203
|
+
nextSteps: [nextStep],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
|
|
7
|
+
function escapeRegex(input) {
|
|
8
|
+
return input.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function globToRegExp(glob) {
|
|
12
|
+
const normalized = glob.replace(/\\/g, '/').trim();
|
|
13
|
+
let pattern = '^';
|
|
14
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
15
|
+
const current = normalized[i];
|
|
16
|
+
const next = normalized[i + 1];
|
|
17
|
+
if (current === '*') {
|
|
18
|
+
if (next === '*') {
|
|
19
|
+
const afterNext = normalized[i + 2];
|
|
20
|
+
if (afterNext === '/') {
|
|
21
|
+
pattern += '(?:.*/)?';
|
|
22
|
+
i += 2;
|
|
23
|
+
} else {
|
|
24
|
+
pattern += '.*';
|
|
25
|
+
i += 1;
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
pattern += '[^/]*';
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (current === '?') {
|
|
33
|
+
pattern += '[^/]';
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
pattern += escapeRegex(current);
|
|
37
|
+
}
|
|
38
|
+
pattern += '$';
|
|
39
|
+
return new RegExp(pattern);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compileMatchers(patterns) {
|
|
43
|
+
return (patterns || []).map((pattern) => ({
|
|
44
|
+
pattern,
|
|
45
|
+
regex: globToRegExp(pattern),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function matchesAny(filePath, matchers) {
|
|
50
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
51
|
+
for (const matcher of matchers) {
|
|
52
|
+
if (matcher.regex.test(normalized)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toSeverity(value, warningThreshold, errorThreshold) {
|
|
60
|
+
if (typeof errorThreshold === 'number' && value >= errorThreshold) {
|
|
61
|
+
return 'error';
|
|
62
|
+
}
|
|
63
|
+
if (typeof warningThreshold === 'number' && value >= warningThreshold) {
|
|
64
|
+
return 'warning';
|
|
65
|
+
}
|
|
66
|
+
return 'none';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function maxSeverity(left, right) {
|
|
70
|
+
const rank = {
|
|
71
|
+
none: 0,
|
|
72
|
+
warning: 1,
|
|
73
|
+
error: 2,
|
|
74
|
+
};
|
|
75
|
+
return rank[left] >= rank[right] ? left : right;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseDateMaybe(input) {
|
|
79
|
+
if (!input) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (input instanceof Date) {
|
|
83
|
+
return Number.isNaN(input.getTime()) ? null : input;
|
|
84
|
+
}
|
|
85
|
+
const parsed = new Date(String(input));
|
|
86
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function countLines(buffer) {
|
|
93
|
+
if (!buffer || buffer.length === 0) {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
let lines = 1;
|
|
97
|
+
for (const byte of buffer) {
|
|
98
|
+
if (byte === 10) {
|
|
99
|
+
lines += 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel) {
|
|
106
|
+
const parsed = inlineConfig ?? (() => {
|
|
107
|
+
const configPath = path.join(cwd, relativePath);
|
|
108
|
+
if (!fs.existsSync(configPath)) {
|
|
109
|
+
throw new Error(`budget config not found: ${relativePath}`);
|
|
110
|
+
}
|
|
111
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
112
|
+
return YAML.parse(raw);
|
|
113
|
+
})();
|
|
114
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
115
|
+
throw new Error(`invalid budget config format: ${relativePath}`);
|
|
116
|
+
}
|
|
117
|
+
if (!parsed.profiles || typeof parsed.profiles !== 'object') {
|
|
118
|
+
throw new Error(`budget config missing profiles: ${relativePath}`);
|
|
119
|
+
}
|
|
120
|
+
const defaultProfile = String(parsed.default_profile || 'production');
|
|
121
|
+
if (!parsed.profiles[defaultProfile]) {
|
|
122
|
+
throw new Error(`default profile not found in profiles: ${defaultProfile}`);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
configPath: configPathLabel || relativePath,
|
|
126
|
+
parsed,
|
|
127
|
+
defaultProfile,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function listTrackedFiles(cwd) {
|
|
132
|
+
const output = execSync('git ls-files -z', {
|
|
133
|
+
cwd,
|
|
134
|
+
encoding: 'utf8',
|
|
135
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
136
|
+
});
|
|
137
|
+
return output
|
|
138
|
+
.split('\u0000')
|
|
139
|
+
.map((value) => value.trim())
|
|
140
|
+
.filter((value) => value.length > 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveProfile(filePath, classifierMatchers, defaultProfile) {
|
|
144
|
+
for (const classifier of classifierMatchers) {
|
|
145
|
+
if (matchesAny(filePath, classifier.matchers)) {
|
|
146
|
+
return classifier.profile;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return defaultProfile;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildWaiverMap(waivers) {
|
|
153
|
+
const map = new Map();
|
|
154
|
+
for (const waiver of waivers || []) {
|
|
155
|
+
if (!waiver || typeof waiver !== 'object') {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const filePath = String(waiver.path || '').trim();
|
|
159
|
+
if (!filePath) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
map.set(filePath, {
|
|
163
|
+
path: filePath,
|
|
164
|
+
reason: String(waiver.reason || '').trim(),
|
|
165
|
+
until: parseDateMaybe(waiver.until),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return map;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function waiverAllowedForProfile(profileId) {
|
|
172
|
+
return profileId === 'tests_and_scripts' || profileId === 'tests' || profileId === 'scripts' || profileId === 'generated';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function evaluateAiContextBudget(options = {}) {
|
|
176
|
+
const cwd = options.cwd || process.cwd();
|
|
177
|
+
const configRelativePath = options.configRelativePath || 'config/ai/ai-context-budget.yaml';
|
|
178
|
+
const { parsed, defaultProfile, configPath } = loadBudgetConfig(
|
|
179
|
+
cwd,
|
|
180
|
+
configRelativePath,
|
|
181
|
+
options.config || null,
|
|
182
|
+
options.configPathLabel || null,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const excludeMatchers = compileMatchers(parsed.exclude || []);
|
|
186
|
+
const classifierMatchers = Object.entries(parsed.classifiers || {}).map(([profile, patterns]) => ({
|
|
187
|
+
profile,
|
|
188
|
+
matchers: compileMatchers(patterns || []),
|
|
189
|
+
}));
|
|
190
|
+
const waiverMap = buildWaiverMap(parsed.waivers || []);
|
|
191
|
+
|
|
192
|
+
const files = listTrackedFiles(cwd);
|
|
193
|
+
const rows = [];
|
|
194
|
+
const skippedMissing = [];
|
|
195
|
+
const invalidWaivers = [];
|
|
196
|
+
|
|
197
|
+
for (const [filePath, waiver] of waiverMap.entries()) {
|
|
198
|
+
const absolutePath = path.join(cwd, filePath);
|
|
199
|
+
if (!fs.existsSync(absolutePath)) {
|
|
200
|
+
invalidWaivers.push({
|
|
201
|
+
file: filePath,
|
|
202
|
+
reason: waiver.reason,
|
|
203
|
+
kind: 'missing',
|
|
204
|
+
detail: 'waiver points to a missing path',
|
|
205
|
+
});
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (matchesAny(filePath, excludeMatchers)) {
|
|
209
|
+
invalidWaivers.push({
|
|
210
|
+
file: filePath,
|
|
211
|
+
reason: waiver.reason,
|
|
212
|
+
kind: 'excluded',
|
|
213
|
+
detail: 'waiver targets an excluded path',
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const profileId = resolveProfile(filePath, classifierMatchers, defaultProfile);
|
|
218
|
+
if (!waiverAllowedForProfile(profileId)) {
|
|
219
|
+
invalidWaivers.push({
|
|
220
|
+
file: filePath,
|
|
221
|
+
reason: waiver.reason,
|
|
222
|
+
kind: 'forbidden_profile',
|
|
223
|
+
detail: `waivers are forbidden for profile ${profileId}`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const relativePath of files) {
|
|
229
|
+
if (matchesAny(relativePath, excludeMatchers)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const absolutePath = path.join(cwd, relativePath);
|
|
234
|
+
if (!fs.existsSync(absolutePath)) {
|
|
235
|
+
skippedMissing.push(relativePath);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const stat = fs.statSync(absolutePath);
|
|
239
|
+
if (!stat.isFile()) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const profileId = resolveProfile(relativePath, classifierMatchers, defaultProfile);
|
|
244
|
+
const profile = parsed.profiles[profileId] || parsed.profiles[defaultProfile];
|
|
245
|
+
if (!profile) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const buffer = fs.readFileSync(absolutePath);
|
|
250
|
+
const lines = countLines(buffer);
|
|
251
|
+
const bytes = buffer.byteLength;
|
|
252
|
+
|
|
253
|
+
const linesSeverity = toSeverity(lines, profile.warning_lines, profile.error_lines);
|
|
254
|
+
const bytesSeverity = toSeverity(bytes, profile.warning_bytes, profile.error_bytes);
|
|
255
|
+
const severity = maxSeverity(linesSeverity, bytesSeverity);
|
|
256
|
+
|
|
257
|
+
const waiver = waiverMap.get(relativePath) || null;
|
|
258
|
+
const waiverExpired = waiver?.until ? waiver.until.getTime() < Date.now() : false;
|
|
259
|
+
const waived = Boolean(waiver) && !waiverExpired;
|
|
260
|
+
|
|
261
|
+
rows.push({
|
|
262
|
+
file: relativePath,
|
|
263
|
+
profile: profileId,
|
|
264
|
+
lines,
|
|
265
|
+
bytes,
|
|
266
|
+
severity,
|
|
267
|
+
linesSeverity,
|
|
268
|
+
bytesSeverity,
|
|
269
|
+
warningLines: profile.warning_lines,
|
|
270
|
+
errorLines: profile.error_lines,
|
|
271
|
+
warningBytes: profile.warning_bytes,
|
|
272
|
+
errorBytes: profile.error_bytes,
|
|
273
|
+
waiver,
|
|
274
|
+
waiverExpired,
|
|
275
|
+
waived,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const warnings = rows.filter((row) => row.severity === 'warning');
|
|
280
|
+
const errors = rows.filter((row) => row.severity === 'error' && !row.waived);
|
|
281
|
+
const waivedErrors = rows.filter((row) => row.severity === 'error' && row.waived);
|
|
282
|
+
const expiredWaivers = rows.filter((row) => row.severity === 'error' && row.waiver && row.waiverExpired);
|
|
283
|
+
|
|
284
|
+
rows.sort((left, right) => {
|
|
285
|
+
if (right.lines !== left.lines) {
|
|
286
|
+
return right.lines - left.lines;
|
|
287
|
+
}
|
|
288
|
+
return right.bytes - left.bytes;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
configPath,
|
|
293
|
+
totalTrackedFiles: files.length,
|
|
294
|
+
analyzedFiles: rows.length,
|
|
295
|
+
skippedMissing,
|
|
296
|
+
rows,
|
|
297
|
+
warnings,
|
|
298
|
+
errors,
|
|
299
|
+
waivedErrors,
|
|
300
|
+
expiredWaivers,
|
|
301
|
+
invalidWaivers,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function formatBytes(bytes) {
|
|
306
|
+
const kb = 1024;
|
|
307
|
+
const mb = kb * 1024;
|
|
308
|
+
if (bytes >= mb) {
|
|
309
|
+
return `${(bytes / mb).toFixed(1)}MB`;
|
|
310
|
+
}
|
|
311
|
+
if (bytes >= kb) {
|
|
312
|
+
return `${(bytes / kb).toFixed(1)}KB`;
|
|
313
|
+
}
|
|
314
|
+
return `${bytes}B`;
|
|
315
|
+
}
|