@thinkingsage/kanon 0.8.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/CHANGELOG.md +410 -0
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bridge/mcp-server.cjs +14171 -0
- package/package.json +98 -0
- package/src/adapters/capabilities.ts +178 -0
- package/src/adapters/claude-code.ts +110 -0
- package/src/adapters/cline.ts +98 -0
- package/src/adapters/codex.ts +173 -0
- package/src/adapters/copilot.ts +106 -0
- package/src/adapters/cursor.ts +97 -0
- package/src/adapters/degradation.ts +95 -0
- package/src/adapters/index.ts +324 -0
- package/src/adapters/kiro-frontmatter.ts +139 -0
- package/src/adapters/kiro-inclusion.ts +86 -0
- package/src/adapters/kiro.ts +412 -0
- package/src/adapters/qdeveloper.ts +115 -0
- package/src/adapters/types.ts +81 -0
- package/src/adapters/windsurf.ts +96 -0
- package/src/admin.ts +283 -0
- package/src/asset-conventions.ts +118 -0
- package/src/attribution-backfill.ts +319 -0
- package/src/attribution-report.ts +95 -0
- package/src/attribution.ts +239 -0
- package/src/backends/github.ts +194 -0
- package/src/backends/http.ts +122 -0
- package/src/backends/index.ts +39 -0
- package/src/backends/local.ts +47 -0
- package/src/backends/s3.ts +157 -0
- package/src/backends/types.ts +59 -0
- package/src/base-cache.ts +270 -0
- package/src/browse-ui.ts +3754 -0
- package/src/browse.ts +1038 -0
- package/src/build.ts +1108 -0
- package/src/catalog.ts +204 -0
- package/src/cli-deprecated.ts +29 -0
- package/src/cli.ts +773 -0
- package/src/collection-admin.ts +287 -0
- package/src/collection-builder.ts +464 -0
- package/src/collections.ts +116 -0
- package/src/compatibility.ts +105 -0
- package/src/config.ts +743 -0
- package/src/eval/rubrics/kiro-progressive-steering.ts +841 -0
- package/src/eval.ts +1169 -0
- package/src/file-writer.ts +61 -0
- package/src/format-registry.ts +141 -0
- package/src/guild/auto-updater.ts +163 -0
- package/src/guild/backend-resolver.ts +49 -0
- package/src/guild/cli.ts +592 -0
- package/src/guild/collection-expander.ts +47 -0
- package/src/guild/global-cache.ts +247 -0
- package/src/guild/hook-generator.ts +100 -0
- package/src/guild/manifest.ts +154 -0
- package/src/guild/path-utils.ts +12 -0
- package/src/guild/sync.ts +622 -0
- package/src/guild/version-resolver.ts +42 -0
- package/src/help/metadata.ts +445 -0
- package/src/help/renderer.ts +265 -0
- package/src/help/typo-suggester.ts +25 -0
- package/src/hooks/expression.ts +493 -0
- package/src/hooks/pipeline.ts +141 -0
- package/src/import.ts +773 -0
- package/src/importers/claude-code.ts +134 -0
- package/src/importers/cline.ts +103 -0
- package/src/importers/codex.ts +140 -0
- package/src/importers/copilot.ts +103 -0
- package/src/importers/cursor.ts +105 -0
- package/src/importers/index.ts +390 -0
- package/src/importers/kiro.ts +110 -0
- package/src/importers/qdeveloper.ts +103 -0
- package/src/importers/types.ts +54 -0
- package/src/importers/windsurf.ts +104 -0
- package/src/install.ts +1005 -0
- package/src/manifest-admin.ts +306 -0
- package/src/mcp-bridge.ts +240 -0
- package/src/mutation/delta.ts +50 -0
- package/src/mutation/history.ts +66 -0
- package/src/mutation/operators.ts +524 -0
- package/src/mutation/runner.ts +332 -0
- package/src/new.ts +106 -0
- package/src/outcomes/collision.ts +127 -0
- package/src/outcomes/normalize.ts +208 -0
- package/src/outcomes/registry.ts +173 -0
- package/src/parser.ts +446 -0
- package/src/provenance-backfill-cli.ts +319 -0
- package/src/provenance-backfill.ts +520 -0
- package/src/publish.ts +354 -0
- package/src/reconcile-orchestrator.ts +502 -0
- package/src/reconcile-report-renderer.ts +176 -0
- package/src/resolve-body.ts +15 -0
- package/src/rosetta/builtins/compatibility-profiles.ts +297 -0
- package/src/rosetta/builtins/contracts.ts +1033 -0
- package/src/rosetta/builtins/pretty-printers/claude-code-native.ts +122 -0
- package/src/rosetta/builtins/pretty-printers/cline-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/codex-native.ts +127 -0
- package/src/rosetta/builtins/pretty-printers/copilot-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/cursor-native.ts +50 -0
- package/src/rosetta/builtins/pretty-printers/index.ts +81 -0
- package/src/rosetta/builtins/pretty-printers/kiro-native.ts +166 -0
- package/src/rosetta/builtins/pretty-printers/kiro-power.ts +108 -0
- package/src/rosetta/builtins/pretty-printers/kiro-skill.ts +88 -0
- package/src/rosetta/builtins/pretty-printers/qdeveloper-native.ts +51 -0
- package/src/rosetta/builtins/pretty-printers/superpowers.ts +97 -0
- package/src/rosetta/builtins/pretty-printers/windsurf-native.ts +50 -0
- package/src/rosetta/builtins/sources/claude-code-native.ts +348 -0
- package/src/rosetta/builtins/sources/cline-native.ts +176 -0
- package/src/rosetta/builtins/sources/codex-native.ts +343 -0
- package/src/rosetta/builtins/sources/copilot-native.ts +178 -0
- package/src/rosetta/builtins/sources/cursor-native.ts +176 -0
- package/src/rosetta/builtins/sources/index.ts +95 -0
- package/src/rosetta/builtins/sources/kiro-native.ts +462 -0
- package/src/rosetta/builtins/sources/kiro-power.ts +285 -0
- package/src/rosetta/builtins/sources/kiro-skill.ts +230 -0
- package/src/rosetta/builtins/sources/qdeveloper-native.ts +181 -0
- package/src/rosetta/builtins/sources/superpowers.ts +240 -0
- package/src/rosetta/builtins/sources/windsurf-native.ts +176 -0
- package/src/rosetta/builtins/targets/claude-code.ts +181 -0
- package/src/rosetta/builtins/targets/cline.ts +87 -0
- package/src/rosetta/builtins/targets/codex.ts +226 -0
- package/src/rosetta/builtins/targets/copilot.ts +103 -0
- package/src/rosetta/builtins/targets/cursor.ts +87 -0
- package/src/rosetta/builtins/targets/index.ts +60 -0
- package/src/rosetta/builtins/targets/kiro.ts +278 -0
- package/src/rosetta/builtins/targets/qdeveloper.ts +103 -0
- package/src/rosetta/builtins/targets/windsurf.ts +87 -0
- package/src/rosetta/canonical.ts +729 -0
- package/src/rosetta/compatibility.ts +432 -0
- package/src/rosetta/contracts.ts +329 -0
- package/src/rosetta/detector.ts +724 -0
- package/src/rosetta/diagnostics.ts +630 -0
- package/src/rosetta/engine-bootstrap.ts +103 -0
- package/src/rosetta/engine.ts +744 -0
- package/src/rosetta/index.ts +381 -0
- package/src/rosetta/inspection.ts +530 -0
- package/src/rosetta/plan.ts +448 -0
- package/src/rosetta/provenance-digest.ts +369 -0
- package/src/rosetta/reconcile.ts +812 -0
- package/src/rosetta/redaction.ts +467 -0
- package/src/rosetta/registry.ts +712 -0
- package/src/rosetta/renderers.ts +571 -0
- package/src/rosetta/request-guard.ts +335 -0
- package/src/rosetta/resolution.ts +419 -0
- package/src/rosetta/source-accounting.ts +233 -0
- package/src/rosetta/templates.ts +129 -0
- package/src/rosetta-cli.ts +717 -0
- package/src/rosetta-docs-generator.ts +793 -0
- package/src/rosetta-profiles-cli.ts +367 -0
- package/src/schemas.ts +1712 -0
- package/src/spec-coordination.ts +1141 -0
- package/src/temper.ts +747 -0
- package/src/template-bundle-loader.ts +312 -0
- package/src/template-engine.ts +53 -0
- package/src/translation-application-policy.ts +496 -0
- package/src/translation-orchestrator.ts +1013 -0
- package/src/translation-plan-applier.ts +473 -0
- package/src/tutorial.ts +305 -0
- package/src/validate.ts +1093 -0
- package/src/versioning.ts +553 -0
- package/src/wizard.ts +660 -0
- package/src/workspace.ts +237 -0
- package/templates/eval-contexts/claude-code.md.njk +6 -0
- package/templates/eval-contexts/cline.md.njk +6 -0
- package/templates/eval-contexts/copilot.md.njk +6 -0
- package/templates/eval-contexts/cursor.md.njk +6 -0
- package/templates/eval-contexts/kiro.md.njk +10 -0
- package/templates/eval-contexts/qdeveloper.md.njk +6 -0
- package/templates/eval-contexts/windsurf.md.njk +6 -0
- package/templates/harness-adapters/_base/attribution-footer.md.njk +17 -0
- package/templates/harness-adapters/_base/base.md.njk +16 -0
- package/templates/harness-adapters/claude-code/claude.md.njk +1 -0
- package/templates/harness-adapters/claude-code/mcp.json.njk +1 -0
- package/templates/harness-adapters/claude-code/settings.json.njk +1 -0
- package/templates/harness-adapters/claude-code/skill-library-index.md.njk +13 -0
- package/templates/harness-adapters/claude-code/skill.md.njk +19 -0
- package/templates/harness-adapters/cline/hook.sh.njk +4 -0
- package/templates/harness-adapters/cline/mcp.json.njk +1 -0
- package/templates/harness-adapters/cline/rule.md.njk +1 -0
- package/templates/harness-adapters/codex/agents-md.md.njk +6 -0
- package/templates/harness-adapters/codex/agents-pointer.md.njk +16 -0
- package/templates/harness-adapters/codex/skill.md.njk +27 -0
- package/templates/harness-adapters/copilot/agents.md.njk +1 -0
- package/templates/harness-adapters/copilot/instructions.md.njk +1 -0
- package/templates/harness-adapters/copilot/scoped.md.njk +6 -0
- package/templates/harness-adapters/cursor/mcp.json.njk +1 -0
- package/templates/harness-adapters/cursor/rule.md.njk +6 -0
- package/templates/harness-adapters/kiro/hook.json.njk +1 -0
- package/templates/harness-adapters/kiro/mcp.json.njk +1 -0
- package/templates/harness-adapters/kiro/power-steering.md.njk +3 -0
- package/templates/harness-adapters/kiro/power.md.njk +12 -0
- package/templates/harness-adapters/kiro/steering.md.njk +16 -0
- package/templates/harness-adapters/qdeveloper/agent.md.njk +1 -0
- package/templates/harness-adapters/qdeveloper/mcp.json.njk +1 -0
- package/templates/harness-adapters/qdeveloper/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/mcp.json.njk +1 -0
- package/templates/harness-adapters/windsurf/rule.md.njk +1 -0
- package/templates/harness-adapters/windsurf/workflow.md.njk +1 -0
- package/templates/knowledge/hooks.yaml.njk +4 -0
- package/templates/knowledge/knowledge.md.njk +53 -0
- package/templates/knowledge/mcp-servers.yaml.njk +2 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosetta Stone — Human and Versioned JSON Renderers
|
|
3
|
+
*
|
|
4
|
+
* Both renderers consume the same InspectionReport model and produce
|
|
5
|
+
* different views of the same data:
|
|
6
|
+
* - Human renderer: terminal-friendly text with optional ANSI color codes
|
|
7
|
+
* - JSON renderer: versioned, schema-valid, deterministic JSON without ANSI
|
|
8
|
+
*
|
|
9
|
+
* CONSTRAINTS:
|
|
10
|
+
* - NO filesystem, process, clock, random, Git, or network imports
|
|
11
|
+
* - Pure functions only (timestamp and registryVersion supplied by caller)
|
|
12
|
+
* - JSON output validates through InspectionReportEnvelopeSchema
|
|
13
|
+
* - JSON output NEVER contains ANSI escape sequences
|
|
14
|
+
* - JSON output uses stable field names and deterministic key ordering
|
|
15
|
+
*
|
|
16
|
+
* Requirements: 8.6, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
AppliedDefault,
|
|
21
|
+
AppliedNormalization,
|
|
22
|
+
DegradationRecord,
|
|
23
|
+
InspectionReportEnvelope,
|
|
24
|
+
TranslationDiagnostic,
|
|
25
|
+
TranslationRequest,
|
|
26
|
+
} from "../schemas";
|
|
27
|
+
import { InspectionReportEnvelopeSchema } from "../schemas";
|
|
28
|
+
import { codePointCompare, stableJsonStringify } from "./contracts";
|
|
29
|
+
import type { InspectionReport } from "./inspection";
|
|
30
|
+
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
// ANSI Color Helpers (human renderer only)
|
|
33
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
34
|
+
|
|
35
|
+
const ANSI = {
|
|
36
|
+
reset: "\x1b[0m",
|
|
37
|
+
bold: "\x1b[1m",
|
|
38
|
+
dim: "\x1b[2m",
|
|
39
|
+
red: "\x1b[31m",
|
|
40
|
+
green: "\x1b[32m",
|
|
41
|
+
yellow: "\x1b[33m",
|
|
42
|
+
blue: "\x1b[34m",
|
|
43
|
+
magenta: "\x1b[35m",
|
|
44
|
+
cyan: "\x1b[36m",
|
|
45
|
+
gray: "\x1b[90m",
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
function color(text: string, ...codes: string[]): string {
|
|
49
|
+
return `${codes.join("")}${text}${ANSI.reset}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// Severity Indicators
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
const SEVERITY_ICON: Record<string, string> = {
|
|
57
|
+
error: "✗",
|
|
58
|
+
warning: "⚠",
|
|
59
|
+
info: "ℹ",
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const SEVERITY_COLOR: Record<string, string> = {
|
|
63
|
+
error: ANSI.red,
|
|
64
|
+
warning: ANSI.yellow,
|
|
65
|
+
info: ANSI.blue,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
69
|
+
// Human Renderer
|
|
70
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Renders an InspectionReport as human-readable terminal text.
|
|
74
|
+
* Uses ANSI color codes for severity indicators and section headers.
|
|
75
|
+
*/
|
|
76
|
+
export function renderHuman(report: InspectionReport): string {
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
|
|
79
|
+
// Header
|
|
80
|
+
lines.push(color("Rosetta Stone Inspection Report", ANSI.bold, ANSI.cyan));
|
|
81
|
+
lines.push(color("─".repeat(40), ANSI.dim));
|
|
82
|
+
lines.push("");
|
|
83
|
+
|
|
84
|
+
// Request summary
|
|
85
|
+
lines.push(color("Request", ANSI.bold));
|
|
86
|
+
lines.push(` Direction: ${report.request.direction}`);
|
|
87
|
+
if (report.request.sourceFormat) {
|
|
88
|
+
lines.push(` Source format: ${report.request.sourceFormat}`);
|
|
89
|
+
}
|
|
90
|
+
if (report.request.targetFormat) {
|
|
91
|
+
lines.push(` Target format: ${report.request.targetFormat}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push(` Strict: ${report.request.strict ? "yes" : "no"}`);
|
|
94
|
+
lines.push(` Dry-run: ${report.request.dryRun ? "yes" : "no"}`);
|
|
95
|
+
lines.push("");
|
|
96
|
+
|
|
97
|
+
// Detection
|
|
98
|
+
if (report.detection) {
|
|
99
|
+
lines.push(color("Detection", ANSI.bold));
|
|
100
|
+
if (report.detection.selectedFormatId) {
|
|
101
|
+
lines.push(
|
|
102
|
+
` Selected: ${color(report.detection.selectedFormatId, ANSI.green)}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (report.detection.ambiguous) {
|
|
106
|
+
lines.push(color(" ⚠ Ambiguous detection", ANSI.yellow));
|
|
107
|
+
}
|
|
108
|
+
for (const candidate of report.detection.candidates) {
|
|
109
|
+
const conf = (candidate.confidence * 100).toFixed(0);
|
|
110
|
+
lines.push(
|
|
111
|
+
` ${candidate.formatId} (${conf}% confidence, ${candidate.evidenceCount} evidence)`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
lines.push("");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Format
|
|
118
|
+
lines.push(color("Format", ANSI.bold));
|
|
119
|
+
lines.push(` ID: ${report.format.formatId}`);
|
|
120
|
+
if (report.format.variant) {
|
|
121
|
+
lines.push(` Variant: ${report.format.variant}`);
|
|
122
|
+
}
|
|
123
|
+
lines.push(` Contract version: ${report.format.contractVersion}`);
|
|
124
|
+
if (report.format.canonicalVersionRange) {
|
|
125
|
+
lines.push(
|
|
126
|
+
` Canonical version range: ${report.format.canonicalVersionRange}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
lines.push(` Lifecycle: ${report.format.lifecycle}`);
|
|
130
|
+
lines.push("");
|
|
131
|
+
|
|
132
|
+
// Canonical summary
|
|
133
|
+
lines.push(color("Canonical", ANSI.bold));
|
|
134
|
+
lines.push(` Schema version: ${report.canonical.schemaVersion}`);
|
|
135
|
+
if (report.canonical.artifactName) {
|
|
136
|
+
lines.push(` Artifact: ${report.canonical.artifactName}`);
|
|
137
|
+
}
|
|
138
|
+
if (report.canonical.type) {
|
|
139
|
+
lines.push(` Type: ${report.canonical.type}`);
|
|
140
|
+
}
|
|
141
|
+
if (report.canonical.harnesses.length > 0) {
|
|
142
|
+
lines.push(` Harnesses: ${report.canonical.harnesses.join(", ")}`);
|
|
143
|
+
}
|
|
144
|
+
const counts = [
|
|
145
|
+
report.canonical.hookCount > 0 && `${report.canonical.hookCount} hook(s)`,
|
|
146
|
+
report.canonical.mcpServerCount > 0 &&
|
|
147
|
+
`${report.canonical.mcpServerCount} MCP server(s)`,
|
|
148
|
+
report.canonical.workflowCount > 0 &&
|
|
149
|
+
`${report.canonical.workflowCount} workflow(s)`,
|
|
150
|
+
report.canonical.bodyOverrideCount > 0 &&
|
|
151
|
+
`${report.canonical.bodyOverrideCount} body override(s)`,
|
|
152
|
+
].filter(Boolean);
|
|
153
|
+
if (counts.length > 0) {
|
|
154
|
+
lines.push(` Content: ${counts.join(", ")}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push("");
|
|
157
|
+
|
|
158
|
+
// Compatibility
|
|
159
|
+
lines.push(color("Compatibility", ANSI.bold));
|
|
160
|
+
const totalCaps =
|
|
161
|
+
report.compatibility.fullCount +
|
|
162
|
+
report.compatibility.partialCount +
|
|
163
|
+
report.compatibility.noneCount;
|
|
164
|
+
lines.push(
|
|
165
|
+
` Full: ${report.compatibility.fullCount} Partial: ${report.compatibility.partialCount} None: ${report.compatibility.noneCount} (${totalCaps} total)`,
|
|
166
|
+
);
|
|
167
|
+
if (report.compatibility.strictPromoted) {
|
|
168
|
+
lines.push(
|
|
169
|
+
color(" Strict mode promoted degradations to errors", ANSI.yellow),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const degradationEntries = Object.entries(report.compatibility.degradations);
|
|
173
|
+
if (degradationEntries.length > 0) {
|
|
174
|
+
lines.push(" Degradations:");
|
|
175
|
+
for (const [capability, action] of degradationEntries) {
|
|
176
|
+
lines.push(` ${capability}: ${action}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
lines.push("");
|
|
180
|
+
|
|
181
|
+
// Options with origins
|
|
182
|
+
const effectiveKeys = Object.keys(report.options.effective);
|
|
183
|
+
if (effectiveKeys.length > 0) {
|
|
184
|
+
lines.push(color("Options", ANSI.bold));
|
|
185
|
+
for (const key of effectiveKeys) {
|
|
186
|
+
const value = report.options.effective[key];
|
|
187
|
+
const origin = report.options.origins[key] ?? "unknown";
|
|
188
|
+
const originLabel = color(`(${origin})`, ANSI.dim);
|
|
189
|
+
lines.push(` ${key}: ${JSON.stringify(value)} ${originLabel}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push("");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Diagnostics
|
|
195
|
+
lines.push(color("Diagnostics", ANSI.bold));
|
|
196
|
+
lines.push(
|
|
197
|
+
` Errors: ${report.diagnostics.errorCount} Warnings: ${report.diagnostics.warningCount} Info: ${report.diagnostics.infoCount}`,
|
|
198
|
+
);
|
|
199
|
+
if (report.diagnostics.blocking.length > 0) {
|
|
200
|
+
lines.push(
|
|
201
|
+
color(` Blocking: ${report.diagnostics.blocking.join(", ")}`, ANSI.red),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
for (const diag of report.diagnostics.diagnostics) {
|
|
205
|
+
const icon = SEVERITY_ICON[diag.severity] ?? "?";
|
|
206
|
+
const colorCode = SEVERITY_COLOR[diag.severity] ?? "";
|
|
207
|
+
const loc = formatDiagnosticLocation(diag);
|
|
208
|
+
lines.push(
|
|
209
|
+
` ${color(icon, colorCode)} [${diag.code}] ${diag.message}${loc}`,
|
|
210
|
+
);
|
|
211
|
+
lines.push(color(` Remediation: ${diag.remediation}`, ANSI.dim));
|
|
212
|
+
}
|
|
213
|
+
lines.push("");
|
|
214
|
+
|
|
215
|
+
// Plan summary
|
|
216
|
+
lines.push(color("Plan", ANSI.bold));
|
|
217
|
+
lines.push(` Operations: ${report.plan.operationCount}`);
|
|
218
|
+
lines.push(` Application state: ${report.plan.applicationState}`);
|
|
219
|
+
if (report.plan.outputFilePaths.length > 0) {
|
|
220
|
+
lines.push(" Output files:");
|
|
221
|
+
for (const path of report.plan.outputFilePaths) {
|
|
222
|
+
lines.push(` ${path}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (report.plan.policyDiagnosticCodes.length > 0) {
|
|
226
|
+
lines.push(
|
|
227
|
+
` Policy diagnostics: ${report.plan.policyDiagnosticCodes.join(", ")}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
lines.push("");
|
|
231
|
+
|
|
232
|
+
// Content preview status
|
|
233
|
+
lines.push(color("Content Preview", ANSI.bold));
|
|
234
|
+
if (report.preview.available) {
|
|
235
|
+
lines.push(color(" Available", ANSI.green));
|
|
236
|
+
} else {
|
|
237
|
+
lines.push(color(" Unavailable", ANSI.yellow));
|
|
238
|
+
if (report.preview.reason) {
|
|
239
|
+
lines.push(` Reason: ${report.preview.reason}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
lines.push("");
|
|
243
|
+
|
|
244
|
+
// Collision policy
|
|
245
|
+
lines.push(color("Collision Policy", ANSI.bold));
|
|
246
|
+
lines.push(` Policy: ${report.collision.policy}`);
|
|
247
|
+
lines.push(` Collisions: ${report.collision.collisionCount}`);
|
|
248
|
+
const outcomeEntries = Object.entries(report.collision.outcomes);
|
|
249
|
+
if (outcomeEntries.length > 0) {
|
|
250
|
+
lines.push(" Outcomes:");
|
|
251
|
+
for (const [path, outcome] of outcomeEntries) {
|
|
252
|
+
lines.push(` ${path}: ${outcome}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return lines.join("\n");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Format a diagnostic's source/canonical location for human display.
|
|
261
|
+
*/
|
|
262
|
+
function formatDiagnosticLocation(diag: TranslationDiagnostic): string {
|
|
263
|
+
if (diag.source?.path) {
|
|
264
|
+
const loc = diag.source;
|
|
265
|
+
const parts = [loc.path];
|
|
266
|
+
if (loc.line !== undefined) {
|
|
267
|
+
parts.push(`:${loc.line}`);
|
|
268
|
+
if (loc.column !== undefined) {
|
|
269
|
+
parts.push(`:${loc.column}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return ` at ${parts.join("")}`;
|
|
273
|
+
}
|
|
274
|
+
if (diag.canonical?.fieldPath) {
|
|
275
|
+
return ` at canonical:${diag.canonical.fieldPath}`;
|
|
276
|
+
}
|
|
277
|
+
return "";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
281
|
+
// JSON Renderer
|
|
282
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
283
|
+
|
|
284
|
+
/** Options required by the JSON renderer that come from the impure shell */
|
|
285
|
+
export interface JsonRenderOptions {
|
|
286
|
+
/** ISO-8601 timestamp for the generatedAt field */
|
|
287
|
+
readonly generatedAt: string;
|
|
288
|
+
/** Registry version string */
|
|
289
|
+
readonly registryVersion: string;
|
|
290
|
+
/** The original translation request (for envelope) */
|
|
291
|
+
readonly request: TranslationRequest;
|
|
292
|
+
/** Defaults applied during translation */
|
|
293
|
+
readonly defaults: readonly AppliedDefault[];
|
|
294
|
+
/** Normalizations applied during translation */
|
|
295
|
+
readonly normalizations: readonly AppliedNormalization[];
|
|
296
|
+
/** Degradation records from compatibility */
|
|
297
|
+
readonly degradations: readonly DegradationRecord[];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Renders an InspectionReport as versioned JSON.
|
|
302
|
+
*
|
|
303
|
+
* Guarantees:
|
|
304
|
+
* - Validates through InspectionReportEnvelopeSchema
|
|
305
|
+
* - Uses stable field names and deterministic key ordering (recursively sorted)
|
|
306
|
+
* - NEVER contains ANSI escape sequences
|
|
307
|
+
* - Includes machineSchemaVersion field
|
|
308
|
+
* - Diagnostics follow severity/phase/code ordering (inherited from report)
|
|
309
|
+
*
|
|
310
|
+
* Returns the serialized JSON string.
|
|
311
|
+
* Throws if the output fails schema validation.
|
|
312
|
+
*/
|
|
313
|
+
export function renderJson(
|
|
314
|
+
report: InspectionReport,
|
|
315
|
+
options: JsonRenderOptions,
|
|
316
|
+
): string {
|
|
317
|
+
// Build the envelope structure from the InspectionReport model
|
|
318
|
+
const envelope: InspectionReportEnvelope = buildEnvelope(report, options);
|
|
319
|
+
|
|
320
|
+
// Validate against the schema
|
|
321
|
+
const result = InspectionReportEnvelopeSchema.safeParse(envelope);
|
|
322
|
+
if (!result.success) {
|
|
323
|
+
const errors = result.error.issues.map(
|
|
324
|
+
(issue) => `${issue.path.join(".")}: ${issue.message}`,
|
|
325
|
+
);
|
|
326
|
+
throw new Error(
|
|
327
|
+
`InspectionReportEnvelope validation failed:\n${errors.join("\n")}`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Produce deterministic JSON with recursively sorted keys
|
|
332
|
+
return stableJsonStringify(result.data);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Renders an InspectionReport as a parsed JSON object (not stringified).
|
|
337
|
+
* Useful when the caller needs the envelope for further processing.
|
|
338
|
+
*
|
|
339
|
+
* Same guarantees as renderJson but returns the validated object.
|
|
340
|
+
*/
|
|
341
|
+
export function renderJsonObject(
|
|
342
|
+
report: InspectionReport,
|
|
343
|
+
options: JsonRenderOptions,
|
|
344
|
+
): InspectionReportEnvelope {
|
|
345
|
+
const envelope = buildEnvelope(report, options);
|
|
346
|
+
|
|
347
|
+
const result = InspectionReportEnvelopeSchema.safeParse(envelope);
|
|
348
|
+
if (!result.success) {
|
|
349
|
+
const errors = result.error.issues.map(
|
|
350
|
+
(issue) => `${issue.path.join(".")}: ${issue.message}`,
|
|
351
|
+
);
|
|
352
|
+
throw new Error(
|
|
353
|
+
`InspectionReportEnvelope validation failed:\n${errors.join("\n")}`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return result.data;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
361
|
+
// Internal Envelope Builder
|
|
362
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
363
|
+
|
|
364
|
+
function buildEnvelope(
|
|
365
|
+
report: InspectionReport,
|
|
366
|
+
options: JsonRenderOptions,
|
|
367
|
+
): InspectionReportEnvelope {
|
|
368
|
+
// Determine request mode from the translation request
|
|
369
|
+
const mode = options.request.mode;
|
|
370
|
+
|
|
371
|
+
// Build source/target format summaries from report.format
|
|
372
|
+
const sourceFormat =
|
|
373
|
+
mode === "inbound" || mode === "transcode"
|
|
374
|
+
? {
|
|
375
|
+
formatId: report.format.formatId,
|
|
376
|
+
...(report.format.variant !== undefined && {
|
|
377
|
+
variant: report.format.variant,
|
|
378
|
+
}),
|
|
379
|
+
contractVersion: report.format.contractVersion,
|
|
380
|
+
lifecycle: report.format.lifecycle,
|
|
381
|
+
}
|
|
382
|
+
: undefined;
|
|
383
|
+
|
|
384
|
+
const targetFormat =
|
|
385
|
+
mode === "outbound" || mode === "transcode"
|
|
386
|
+
? {
|
|
387
|
+
formatId: report.format.formatId,
|
|
388
|
+
...(report.format.variant !== undefined && {
|
|
389
|
+
variant: report.format.variant,
|
|
390
|
+
}),
|
|
391
|
+
contractVersion: report.format.contractVersion,
|
|
392
|
+
lifecycle: report.format.lifecycle,
|
|
393
|
+
}
|
|
394
|
+
: undefined;
|
|
395
|
+
|
|
396
|
+
// Build detection section
|
|
397
|
+
const detection = report.detection
|
|
398
|
+
? {
|
|
399
|
+
candidates: report.detection.candidates.map((c) => ({
|
|
400
|
+
formatId: c.formatId,
|
|
401
|
+
confidence: c.confidence,
|
|
402
|
+
evidence: [], // Condensed in inspection report; empty array for envelope
|
|
403
|
+
})),
|
|
404
|
+
...(report.detection.selectedFormatId !== undefined && {
|
|
405
|
+
selected: report.detection.selectedFormatId,
|
|
406
|
+
}),
|
|
407
|
+
}
|
|
408
|
+
: undefined;
|
|
409
|
+
|
|
410
|
+
// Build canonical section
|
|
411
|
+
const canonical = report.canonical.artifactName
|
|
412
|
+
? {
|
|
413
|
+
artifactName: report.canonical.artifactName,
|
|
414
|
+
fieldCount: countCanonicalFields(report),
|
|
415
|
+
}
|
|
416
|
+
: {
|
|
417
|
+
fieldCount: countCanonicalFields(report),
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Build compatibility section from report degradation data
|
|
421
|
+
const compatibilityEntries = Object.entries(
|
|
422
|
+
report.compatibility.degradations,
|
|
423
|
+
);
|
|
424
|
+
const compatibility =
|
|
425
|
+
compatibilityEntries.length > 0 ||
|
|
426
|
+
report.compatibility.fullCount > 0 ||
|
|
427
|
+
report.compatibility.partialCount > 0 ||
|
|
428
|
+
report.compatibility.noneCount > 0
|
|
429
|
+
? {
|
|
430
|
+
counts: buildCompatibilityCounts(report),
|
|
431
|
+
}
|
|
432
|
+
: undefined;
|
|
433
|
+
|
|
434
|
+
// Build plan section
|
|
435
|
+
const plan =
|
|
436
|
+
report.plan.operationCount > 0
|
|
437
|
+
? {
|
|
438
|
+
fileCount: report.plan.outputFilePaths.length,
|
|
439
|
+
paths: [...report.plan.outputFilePaths].sort(codePointCompare),
|
|
440
|
+
}
|
|
441
|
+
: undefined;
|
|
442
|
+
|
|
443
|
+
// Safe diagnostics — strip any ANSI from messages/remediations
|
|
444
|
+
const diagnostics = report.diagnostics.diagnostics.map(
|
|
445
|
+
stripAnsiFromDiagnostic,
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Sort defaults deterministically
|
|
449
|
+
const defaults = [...options.defaults].sort((a, b) =>
|
|
450
|
+
codePointCompare(a.field, b.field),
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Sort normalizations deterministically
|
|
454
|
+
const normalizations = [...options.normalizations].sort((a, b) =>
|
|
455
|
+
codePointCompare(a.field, b.field),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
// Sort degradations deterministically
|
|
459
|
+
const degradations = [...options.degradations].sort((a, b) =>
|
|
460
|
+
codePointCompare(a.capability, b.capability),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
machineSchemaVersion: "1.0",
|
|
465
|
+
generatedAt: options.generatedAt,
|
|
466
|
+
registryVersion: options.registryVersion,
|
|
467
|
+
request: options.request,
|
|
468
|
+
...(sourceFormat !== undefined && { sourceFormat }),
|
|
469
|
+
...(targetFormat !== undefined && { targetFormat }),
|
|
470
|
+
...(detection !== undefined && { detection }),
|
|
471
|
+
canonical,
|
|
472
|
+
...(compatibility !== undefined && { compatibility }),
|
|
473
|
+
...(plan !== undefined && { plan }),
|
|
474
|
+
defaults,
|
|
475
|
+
normalizations,
|
|
476
|
+
diagnostics,
|
|
477
|
+
degradations,
|
|
478
|
+
} as InspectionReportEnvelope;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Count canonical fields based on the inspection report's canonical summary.
|
|
483
|
+
*/
|
|
484
|
+
function countCanonicalFields(report: InspectionReport): number {
|
|
485
|
+
const c = report.canonical;
|
|
486
|
+
let count = 0;
|
|
487
|
+
if (c.artifactName) count++;
|
|
488
|
+
if (c.type) count++;
|
|
489
|
+
count += c.harnesses.length;
|
|
490
|
+
count += c.hookCount;
|
|
491
|
+
count += c.mcpServerCount;
|
|
492
|
+
count += c.workflowCount;
|
|
493
|
+
count += c.bodyOverrideCount;
|
|
494
|
+
return count;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Build compatibility counts record from report's compatibility summary.
|
|
499
|
+
* Maps each degradation capability to its support level and affected value count.
|
|
500
|
+
*/
|
|
501
|
+
function buildCompatibilityCounts(
|
|
502
|
+
report: InspectionReport,
|
|
503
|
+
): Record<string, { support: string; affectedValues: number }> {
|
|
504
|
+
const counts: Record<string, { support: string; affectedValues: number }> =
|
|
505
|
+
{};
|
|
506
|
+
|
|
507
|
+
// Initialize all canonical capabilities with "full" support
|
|
508
|
+
const ALL_CAPABILITIES = [
|
|
509
|
+
"frontmatter",
|
|
510
|
+
"body",
|
|
511
|
+
"hooks",
|
|
512
|
+
"mcp-servers",
|
|
513
|
+
"workflows",
|
|
514
|
+
"body-overrides",
|
|
515
|
+
"extra-fields",
|
|
516
|
+
"path-scoping",
|
|
517
|
+
"toggleable-rules",
|
|
518
|
+
"file-match-inclusion",
|
|
519
|
+
"system-prompt-merging",
|
|
520
|
+
"skill",
|
|
521
|
+
"power",
|
|
522
|
+
"rule",
|
|
523
|
+
"workflow",
|
|
524
|
+
"agent",
|
|
525
|
+
"prompt",
|
|
526
|
+
"template",
|
|
527
|
+
"reference-pack",
|
|
528
|
+
];
|
|
529
|
+
|
|
530
|
+
for (const cap of ALL_CAPABILITIES) {
|
|
531
|
+
counts[cap] = { support: "full", affectedValues: 0 };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Override with degradation entries from the compatibility summary
|
|
535
|
+
for (const [capability, action] of Object.entries(
|
|
536
|
+
report.compatibility.degradations,
|
|
537
|
+
)) {
|
|
538
|
+
// Infer support level from action
|
|
539
|
+
const support = action === "omit" ? "none" : "partial";
|
|
540
|
+
counts[capability] = { support, affectedValues: 0 };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return counts;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
547
|
+
// ANSI Stripping Utility
|
|
548
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
549
|
+
|
|
550
|
+
/** Regex matching ANSI escape sequences */
|
|
551
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching ANSI escape sequences
|
|
552
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
553
|
+
|
|
554
|
+
/** Strip all ANSI escape sequences from a string */
|
|
555
|
+
export function stripAnsi(text: string): string {
|
|
556
|
+
return text.replace(ANSI_REGEX, "");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Strip ANSI from all string fields in a diagnostic.
|
|
561
|
+
* Returns a new diagnostic with cleaned text.
|
|
562
|
+
*/
|
|
563
|
+
function stripAnsiFromDiagnostic(
|
|
564
|
+
diag: TranslationDiagnostic,
|
|
565
|
+
): TranslationDiagnostic {
|
|
566
|
+
return {
|
|
567
|
+
...diag,
|
|
568
|
+
message: stripAnsi(diag.message),
|
|
569
|
+
remediation: stripAnsi(diag.remediation),
|
|
570
|
+
};
|
|
571
|
+
}
|