@decantr/cli 3.4.2 → 3.5.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.
@@ -562,6 +562,11 @@ function detectPathnameBranchRoutes(content) {
562
562
  if (strongMatches > 0 || hasPathnameSignal) {
563
563
  collectRouteLiterals(/\b(?:href|to)\s*=\s*["'`](\/[^"'`]+)["'`]/g, content, routes);
564
564
  }
565
+ if (hasPathnameSignal && (/\?\s*["'`]\/["'`]\s*:/.test(content) || /\|\|\s*["'`]\/["'`]/.test(content) || /\b(?:defaultRoute|defaultPath|fallbackRoute|fallbackPath)\s*[:=]\s*["'`]\/["'`]/.test(
566
+ content
567
+ ))) {
568
+ routes.add("/");
569
+ }
565
570
  return [...routes];
566
571
  }
567
572
  function hasReactRouterDependency(projectRoot) {
@@ -1441,6 +1446,18 @@ function loadJsonEntries(dir) {
1441
1446
  return [];
1442
1447
  }
1443
1448
  }
1449
+ function readJson(path) {
1450
+ if (!existsSync7(path)) return null;
1451
+ try {
1452
+ return JSON.parse(readFileSync7(path, "utf-8"));
1453
+ } catch {
1454
+ return null;
1455
+ }
1456
+ }
1457
+ function addPattern(registry, id, source, data = null) {
1458
+ if (typeof id !== "string" || !id.trim() || registry.has(id)) return;
1459
+ registry.set(id, data ?? { id, source });
1460
+ }
1444
1461
  function buildGuardRegistryContext(projectRoot = process.cwd()) {
1445
1462
  const themeRegistry = /* @__PURE__ */ new Map();
1446
1463
  const patternRegistry = /* @__PURE__ */ new Map();
@@ -1461,21 +1478,22 @@ function buildGuardRegistryContext(projectRoot = process.cwd()) {
1461
1478
  }
1462
1479
  }
1463
1480
  for (const data of loadJsonEntries(join7(cacheDir, "@official", "patterns"))) {
1464
- if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
1465
- patternRegistry.set(data.id, data);
1466
- }
1481
+ addPattern(patternRegistry, data.id, "cache", data);
1467
1482
  }
1468
1483
  for (const entry of loadBundledContentList("patterns")) {
1469
1484
  const data = entry.data;
1470
1485
  const id = typeof data.id === "string" ? data.id : entry.id;
1471
- if (!patternRegistry.has(id)) {
1472
- patternRegistry.set(id, data);
1473
- }
1486
+ addPattern(patternRegistry, id, "bundled", data);
1474
1487
  }
1475
1488
  for (const data of loadJsonEntries(join7(customDir, "patterns"))) {
1476
- if (typeof data.id === "string") {
1477
- patternRegistry.set(data.id, data);
1478
- }
1489
+ addPattern(patternRegistry, data.id, "custom", data);
1490
+ }
1491
+ const localPatterns = readJson(join7(projectRoot, ".decantr", "local-patterns.json"));
1492
+ const patterns = Array.isArray(localPatterns?.patterns) ? localPatterns.patterns : [];
1493
+ for (const pattern of patterns) {
1494
+ if (!pattern || typeof pattern !== "object") continue;
1495
+ const data = pattern;
1496
+ addPattern(patternRegistry, data.id, "local-law", data);
1479
1497
  }
1480
1498
  return { themeRegistry, patternRegistry };
1481
1499
  }
@@ -1,19 +1,20 @@
1
1
  import {
2
2
  createProjectHealthReport,
3
3
  listWorkspaceAppCandidates
4
- } from "./chunk-PQKTJGYL.js";
4
+ } from "./chunk-T45GFC6F.js";
5
5
 
6
6
  // src/commands/workspace.ts
7
7
  import { execFileSync } from "child_process";
8
8
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
9
9
  import { dirname, join, relative, resolve } from "path";
10
+ import { WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL } from "@decantr/verifier";
10
11
  var BOLD = "\x1B[1m";
11
12
  var DIM = "\x1B[2m";
12
13
  var GREEN = "\x1B[32m";
13
14
  var RED = "\x1B[31m";
14
15
  var YELLOW = "\x1B[33m";
15
16
  var RESET = "\x1B[0m";
16
- var WORKSPACE_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/workspace-health-report.v1.json";
17
+ var WORKSPACE_HEALTH_SCHEMA_URL = WORKSPACE_HEALTH_REPORT_V2_SCHEMA_URL;
17
18
  var DEFAULT_IGNORES = /* @__PURE__ */ new Set([
18
19
  ".git",
19
20
  ".next",
@@ -181,7 +182,9 @@ async function createWorkspaceHealthReport(root = process.cwd(), options = {}) {
181
182
  durationMs: Date.now() - startedAt,
182
183
  changed: options.changedOnly ? projectChanged(project, changed) : false,
183
184
  source: project.source,
184
- error: null
185
+ error: null,
186
+ loopState: report.loop.state,
187
+ loopNextAction: report.loop.nextActions[0] ?? null
185
188
  };
186
189
  } catch (error) {
187
190
  return {
@@ -196,23 +199,44 @@ async function createWorkspaceHealthReport(root = process.cwd(), options = {}) {
196
199
  durationMs: Date.now() - startedAt,
197
200
  changed: options.changedOnly ? projectChanged(project, changed) : false,
198
201
  source: project.source,
199
- error: error.message
202
+ error: error.message,
203
+ loopState: "blocked_missing_context",
204
+ loopNextAction: "Fix the project health failure, then rerun workspace health."
200
205
  };
201
206
  }
202
207
  });
208
+ const summary = {
209
+ projectCount: allProjects.length,
210
+ checkedCount: checked.length,
211
+ healthyCount: checked.filter((project) => project.status === "healthy").length,
212
+ warningCount: checked.filter((project) => project.status === "warning").length,
213
+ errorCount: checked.filter((project) => project.status === "error").length,
214
+ failedCount: checked.filter((project) => project.status === "failed").length
215
+ };
216
+ const blockedCount = checked.filter(
217
+ (project) => project.loopState?.startsWith("blocked") || project.loopState === "human_resolution_required"
218
+ ).length;
219
+ const repairRequiredCount = checked.filter(
220
+ (project) => project.loopState === "repair_required"
221
+ ).length;
222
+ const workspaceLoopState = checked.length === 0 ? "needs_context" : blockedCount > 0 ? "human_resolution_required" : repairRequiredCount > 0 || summary.errorCount > 0 || summary.warningCount > 0 ? "repair_required" : "verified";
223
+ const workspaceLoopStatus = workspaceLoopState === "human_resolution_required" || workspaceLoopState.startsWith("blocked") ? "blocked" : summary.errorCount > 0 || summary.failedCount > 0 ? "error" : summary.warningCount > 0 ? "warning" : "healthy";
203
224
  return {
204
225
  $schema: WORKSPACE_HEALTH_SCHEMA_URL,
205
226
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
206
227
  workspaceRoot,
207
228
  changedOnly: options.changedOnly ?? false,
208
229
  since: options.changedOnly ? since : null,
209
- summary: {
210
- projectCount: allProjects.length,
211
- checkedCount: checked.length,
212
- healthyCount: checked.filter((project) => project.status === "healthy").length,
213
- warningCount: checked.filter((project) => project.status === "warning").length,
214
- errorCount: checked.filter((project) => project.status === "error").length,
215
- failedCount: checked.filter((project) => project.status === "failed").length
230
+ summary,
231
+ loop: {
232
+ state: workspaceLoopState,
233
+ status: workspaceLoopStatus,
234
+ projectCount: checked.length,
235
+ blockedCount,
236
+ repairRequiredCount,
237
+ nextActions: [
238
+ workspaceLoopState === "verified" ? "Workspace loop verified." : 'Open the highest-risk project, run `decantr task <route> "<intent>"`, repair, then rerun `decantr verify`.'
239
+ ]
216
240
  },
217
241
  projects: checked
218
242
  };
@@ -223,6 +247,7 @@ function formatWorkspaceHealthText(report) {
223
247
  "",
224
248
  `Projects: ${report.summary.checkedCount}/${report.summary.projectCount}`,
225
249
  `Healthy: ${report.summary.healthyCount} | Warnings: ${report.summary.warningCount} | Errors: ${report.summary.errorCount} | Failed: ${report.summary.failedCount}`,
250
+ `Loop: ${report.loop.state} | blocked ${report.loop.blockedCount} | repair ${report.loop.repairRequiredCount}`,
226
251
  ""
227
252
  ];
228
253
  for (const project of report.projects) {
@@ -3,7 +3,7 @@ import {
3
3
  sendProjectHealthCiFailedTelemetry,
4
4
  sendProjectHealthPromptTelemetry,
5
5
  sendProjectHealthReportTelemetry
6
- } from "./chunk-2GCVVEQC.js";
6
+ } from "./chunk-PM7DKABI.js";
7
7
 
8
8
  // src/commands/health.ts
9
9
  import { execFileSync as execFileSync2 } from "child_process";
@@ -16,10 +16,14 @@ import {
16
16
  anchorFindingsToGraph,
17
17
  auditProject,
18
18
  buildProjectHealthRepairPlan,
19
+ createAuthorityResolution,
19
20
  createContractAssertions,
20
21
  createEvidenceBundle,
22
+ createEvidenceTier,
23
+ createLoopReadiness,
21
24
  deriveVerificationDiagnostic,
22
- KNOWN_VERIFICATION_DIAGNOSTICS
25
+ KNOWN_VERIFICATION_DIAGNOSTICS,
26
+ PROJECT_HEALTH_REPORT_V2_SCHEMA_URL
23
27
  } from "@decantr/verifier";
24
28
 
25
29
  // src/workspace.ts
@@ -3210,7 +3214,7 @@ var RED2 = "\x1B[31m";
3210
3214
  var GREEN2 = "\x1B[32m";
3211
3215
  var CYAN = "\x1B[36m";
3212
3216
  var YELLOW = "\x1B[33m";
3213
- var PROJECT_HEALTH_SCHEMA_URL = "https://decantr.ai/schemas/project-health-report.v1.json";
3217
+ var PROJECT_HEALTH_SCHEMA_URL = PROJECT_HEALTH_REPORT_V2_SCHEMA_URL;
3214
3218
  var DEFAULT_HEALTH_CI_WORKFLOW_PATH = ".github/workflows/decantr-health.yml";
3215
3219
  var DEFAULT_HEALTH_CI_REPORT_PATH = "decantr-health.md";
3216
3220
  var DEFAULT_HEALTH_CI_JSON_PATH = "decantr-health.json";
@@ -4860,7 +4864,7 @@ async function createProjectHealthReport(projectRoot = process.cwd(), options =
4860
4864
  }));
4861
4865
  const finalCounts = countFindings(repairPlanFindings);
4862
4866
  const commandContext = commandContextForProject(projectRoot);
4863
- return {
4867
+ const baseReport = {
4864
4868
  $schema: PROJECT_HEALTH_SCHEMA_URL,
4865
4869
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4866
4870
  projectRoot,
@@ -4901,6 +4905,33 @@ async function createProjectHealthReport(projectRoot = process.cwd(), options =
4901
4905
  },
4902
4906
  findings: repairPlanFindings
4903
4907
  };
4908
+ const evidenceTier = createEvidenceTier(baseReport, {
4909
+ runtimeProbeCount: browserVerification ? Math.max(1, browserVerification.evidence.screenshots.length) : void 0,
4910
+ visualArtifactCount: browserVerification?.evidence.screenshots.length ?? 0
4911
+ });
4912
+ const authority = createAuthorityResolution(baseReport);
4913
+ const loop = createLoopReadiness(baseReport, authority, evidenceTier);
4914
+ return {
4915
+ ...baseReport,
4916
+ evidenceTier,
4917
+ authority,
4918
+ loop,
4919
+ findings: repairPlanFindings.map((finding) => {
4920
+ const conflict = authority.conflicts.find((entry) => entry.id === finding.id);
4921
+ return {
4922
+ ...finding,
4923
+ evidenceTier,
4924
+ authorityLane: conflict?.lane ?? authority.activeLane,
4925
+ resolutionActions: conflict?.recommendedActions,
4926
+ privacy: {
4927
+ sourceIncluded: false,
4928
+ redacted: true,
4929
+ localOnly: true
4930
+ },
4931
+ loopVerdict: loop.state
4932
+ };
4933
+ })
4934
+ };
4904
4935
  }
4905
4936
  function colorForStatus(status) {
4906
4937
  if (status === "healthy") return GREEN2;
@@ -4954,6 +4985,11 @@ function formatProjectHealthText(report) {
4954
4985
  ` Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"} | review ${report.packs.reviewPackPresent ? "present" : "missing"} | pages ${report.packs.pagePackCount}`,
4955
4986
  ` Graph: ${report.graph.current === null ? "not attached" : report.graph.current ? "current" : "stale"} | capsule ${report.graph.capsulePresent ? "present" : "missing"} | sources ${report.graph.sourceArtifactCount}`,
4956
4987
  "",
4988
+ `${BOLD}Control loop:${RESET2}`,
4989
+ ` State: ${report.loop.state} | evidence ${report.evidenceTier.confidence.level} (${report.evidenceTier.confidence.score})`,
4990
+ ` Authority: ${report.authority.activeLane} \u2014 ${report.authority.summary}`,
4991
+ ` Next: ${report.loop.nextActions[0] ?? report.loop.verifyCommand}`,
4992
+ "",
4957
4993
  `${BOLD}Findings:${RESET2}`
4958
4994
  ];
4959
4995
  if (report.findings.length === 0) {
@@ -4986,6 +5022,7 @@ function formatProjectHealthText(report) {
4986
5022
  }
4987
5023
  lines.push("");
4988
5024
  lines.push(`${BOLD}CI:${RESET2} ${report.ci.recommendedCommand}`);
5025
+ lines.push(`${BOLD}Loop verify:${RESET2} ${report.loop.verifyCommand}`);
4989
5026
  return `${lines.join("\n")}
4990
5027
  `;
4991
5028
  }
@@ -5001,6 +5038,9 @@ function formatProjectHealthMarkdown(report) {
5001
5038
  `- Runtime audit: ${report.summary.runtimeAuditChecked ? report.summary.runtimePassed ? "passed" : "failed" : "not checked"}`,
5002
5039
  `- Packs: manifest ${report.packs.manifestPresent ? "present" : "missing"}, review ${report.packs.reviewPackPresent ? "present" : "missing"}`,
5003
5040
  `- Graph: ${report.graph.current === null ? "not attached" : report.graph.current ? "current" : "stale"}, capsule ${report.graph.capsulePresent ? "present" : "missing"}, sources ${report.graph.sourceArtifactCount}`,
5041
+ `- Loop: **${report.loop.state}** (${report.loop.status})`,
5042
+ `- Evidence tier: **${report.evidenceTier.stage}** / ${report.evidenceTier.confidence.level}`,
5043
+ `- Authority: **${report.authority.activeLane}**`,
5004
5044
  "",
5005
5045
  "## Findings",
5006
5046
  ""
@@ -5084,6 +5124,12 @@ function formatDiagnosticCatalogText() {
5084
5124
  async function createProjectEvidenceBundle(projectRoot, report, options = {}) {
5085
5125
  const audit = await auditProject(projectRoot);
5086
5126
  const assertions = createContractAssertions(projectRoot, audit);
5127
+ const visualManifestPath = join5(projectRoot, ".decantr", "evidence", "visual-manifest.json");
5128
+ const browserEvidence = await browserEvidenceFromOptions(
5129
+ projectRoot,
5130
+ options,
5131
+ report.routes.declared
5132
+ );
5087
5133
  return createEvidenceBundle({
5088
5134
  projectRoot,
5089
5135
  report,
@@ -5091,7 +5137,36 @@ async function createProjectEvidenceBundle(projectRoot, report, options = {}) {
5091
5137
  assertions,
5092
5138
  workspaceConfigPath: existsSync5(join5(projectRoot, ".decantr", "workspace.json")) ? join5(projectRoot, ".decantr", "workspace.json") : null,
5093
5139
  designTokensPath: resolveOptionalPath(projectRoot, options.designTokensPath) ?? null,
5094
- browser: await browserEvidenceFromOptions(projectRoot, options, report.routes.declared),
5140
+ visualManifestPath: existsSync5(visualManifestPath) ? visualManifestPath : null,
5141
+ artifacts: [
5142
+ {
5143
+ id: "artifact:evidence-bundle",
5144
+ kind: "evidence-bundle",
5145
+ path: ".decantr/evidence/evidence-bundle.json",
5146
+ hash: null,
5147
+ localOnly: true,
5148
+ redacted: true
5149
+ },
5150
+ ...existsSync5(visualManifestPath) ? [
5151
+ {
5152
+ id: "artifact:visual-manifest",
5153
+ kind: "visual-manifest",
5154
+ path: ".decantr/evidence/visual-manifest.json",
5155
+ hash: null,
5156
+ localOnly: true,
5157
+ redacted: true
5158
+ }
5159
+ ] : [],
5160
+ ...(browserEvidence?.screenshots ?? []).map((screenshot, index) => ({
5161
+ id: `artifact:screenshot:${index + 1}`,
5162
+ kind: "screenshot",
5163
+ path: screenshot,
5164
+ hash: null,
5165
+ localOnly: true,
5166
+ redacted: false
5167
+ }))
5168
+ ],
5169
+ browser: browserEvidence,
5095
5170
  designTokens: collectDesignTokenEvidence(projectRoot, options.designTokensPath)
5096
5171
  });
5097
5172
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cmdHeal,
3
3
  collectCheckIssues
4
- } from "./chunk-2GCVVEQC.js";
4
+ } from "./chunk-PM7DKABI.js";
5
5
  export {
6
6
  cmdHeal,
7
7
  collectCheckIssues
@@ -13,8 +13,8 @@ import {
13
13
  renderProjectHealthCiWorkflow,
14
14
  shouldFailHealth,
15
15
  writeProjectHealthCiWorkflow
16
- } from "./chunk-PQKTJGYL.js";
17
- import "./chunk-2GCVVEQC.js";
16
+ } from "./chunk-T45GFC6F.js";
17
+ import "./chunk-PM7DKABI.js";
18
18
  export {
19
19
  cmdHealth,
20
20
  collectDesignTokenEvidence,
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import "./chunk-32WTNJQU.js";
1
+ import "./chunk-LTNGAAV4.js";
2
2
  import "./chunk-SIDKK73N.js";
3
- import "./chunk-BDA6TWV3.js";
4
- import "./chunk-PQKTJGYL.js";
5
- import "./chunk-2GCVVEQC.js";
3
+ import "./chunk-RJ6PT7WP.js";
4
+ import "./chunk-T45GFC6F.js";
5
+ import "./chunk-PM7DKABI.js";