@formigio/fazemos-cli 0.10.11 → 0.10.12

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/dist/index.js CHANGED
@@ -7,6 +7,9 @@ getActiveProjectId, setActiveProjectId, clearActiveProjectId, findProjectBySlug,
7
7
  import { login, signup, confirmSignup, adminLogin } from './auth.js';
8
8
  import { api, ApiError, refreshAuthMeCache, invalidateAuthMeCache } from './api.js';
9
9
  import { isProjectConnectionUnavailable, renderProjectConnectionUnavailableCopy, } from './connectionErrorCopy.js';
10
+ import { loadYaml, summarize } from './yaml/load.js';
11
+ import { printFindings, printJson } from './yaml/format.js';
12
+ import { validateManifest } from './manifest/checks.js';
10
13
  import { execSync } from 'child_process';
11
14
  import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
12
15
  import { fileURLToPath } from 'url';
@@ -7453,6 +7456,135 @@ docs
7453
7456
  handleScopedError(err);
7454
7457
  }
7455
7458
  });
7459
+ // ── Spec-artifact validation (TOOL-1) ──────────────────────────────────
7460
+ //
7461
+ // Two top-level groups:
7462
+ // fazemos yaml validate <path> Generic YAML parse check
7463
+ // fazemos manifest validate <path> Architecture Design Manifest check
7464
+ //
7465
+ // Both support --json for structured output. Exit code 1 on errors, 0 on
7466
+ // warnings/info only. See CLAUDE.md "TOOL-1" for scope.
7467
+ const yamlGroup = program.command('yaml').description('Generic YAML file utilities. Offline; no API calls. Use as a cheap ' +
7468
+ 'author-time or CI gate before committing a YAML file or feeding it into ' +
7469
+ 'another tool. For Architecture Design Manifests, prefer `fazemos manifest ' +
7470
+ 'validate` instead — it runs YAML parse plus structural and custom checks.');
7471
+ yamlGroup
7472
+ .command('validate')
7473
+ .description('Parse a YAML file and report parse errors with line/column. Cheap pre-commit / CI gate.')
7474
+ .argument('<path>', 'Path to a YAML file (.yaml or .yml). Absolute or relative to CWD.')
7475
+ .option('--json', 'Emit structured JSON output instead of human-readable text. See --help for shape.')
7476
+ .addHelpText('after', `
7477
+ Rule slugs (stable; safe to grep in --json output):
7478
+ read.not_found File at <path> does not exist
7479
+ read.not_a_file <path> exists but is not a regular file
7480
+ read.io_error Read failed (permissions, etc.)
7481
+ yaml.parse_error js-yaml could not parse the document
7482
+
7483
+ Exit codes:
7484
+ 0 YAML parsed cleanly (no error findings)
7485
+ 1 Parse or read error finding present
7486
+
7487
+ JSON output shape (--json):
7488
+ {
7489
+ "source": "<path>",
7490
+ "ok": true | false,
7491
+ "summary": { "errors": <int>, "warnings": <int>, "infos": <int> },
7492
+ "findings": [
7493
+ { "severity": "error"|"warning"|"info",
7494
+ "rule": "<rule-slug>",
7495
+ "message": "<human-readable>",
7496
+ "path": "<dotted-path-into-doc>", // optional
7497
+ "line": <int>, // optional (parse errors)
7498
+ "column": <int> } // optional (parse errors)
7499
+ ]
7500
+ }
7501
+
7502
+ Examples:
7503
+ fazemos yaml validate ./config.yaml
7504
+ fazemos yaml validate ./fixture.yml --json | jq '.findings[]'`)
7505
+ .action((path, opts) => {
7506
+ const result = loadYaml(path);
7507
+ const summary = summarize(result.findings);
7508
+ if (opts.json)
7509
+ printJson(result.source, result.findings, summary);
7510
+ else
7511
+ printFindings(result.source, result.findings, summary);
7512
+ if (summary.errors > 0)
7513
+ process.exit(1);
7514
+ });
7515
+ const manifestGroup = program.command('manifest').description('Architecture Design Manifest validation (the YAML companion to a tech ' +
7516
+ 'spec, conventionally at specs/tech/<area>/<feature>-manifest.yaml). ' +
7517
+ 'Offline; no API calls. Designed to run at three points: (1) author time ' +
7518
+ 'before commit, (2) in agent prompts (Marco / Dex) before review tokens ' +
7519
+ 'are spent, (3) in CI on PRs touching a manifest. Catches the recurring ' +
7520
+ 'miss class — audit-summary math, AC traceability, duplicate IDs — ' +
7521
+ 'without a workspace-level toolchain.');
7522
+ manifestGroup
7523
+ .command('validate')
7524
+ .description('Validate an Architecture Design Manifest: YAML parse + structural schema + custom checks (audit math, AC traceability, duplicate IDs).')
7525
+ .argument('<path>', 'Path to a manifest YAML file. Convention: specs/tech/<area>/<feature>-manifest.yaml inside a workspace clone. Absolute paths also work.')
7526
+ .option('--json', 'Emit structured JSON output instead of human-readable text. See --help for shape.')
7527
+ .addHelpText('after', `
7528
+ What runs (in order):
7529
+ 1. js-yaml parse (rule slug: yaml.parse_error)
7530
+ 2. permissive structural schema (ajv) (rule slugs: schema.*)
7531
+ 3. custom checks (rule slugs: audit.* / trace.* / ids.* / manifest.*)
7532
+
7533
+ The schema requires feature.id + feature.title. It accepts both manifest shape
7534
+ variants seen in the wild — entry_points as map ({added, modified, replaced,
7535
+ unchanged, audit_summary}) OR as a flat list, traceability values as string OR
7536
+ object OR array-of-rows, rules as map OR array-of-{id, ...} entries.
7537
+
7538
+ Rule slugs (stable; safe to grep in --json output):
7539
+ audit.sum_mismatch audit_summary.{added+modified+replaced+unchanged} ≠ total_in_this_manifest
7540
+ audit.count_vs_array audit_summary declared count ≠ actual entry_points.<bucket>[] length
7541
+ trace.ac_task_missing traceability.AC*.task references a task ID with no match in implementation.*.tasks
7542
+ (recurses through implementation.sequencing.* and similar nested buckets)
7543
+ ids.duplicate_task same task id in multiple implementation buckets
7544
+ ids.duplicate duplicate id in edge_cases / risks / phase_1_gate.questions
7545
+ helper.callers_duplicate (warning) api.internal_helper.callers lists same caller twice
7546
+ schema.required required field missing (feature.id, feature.title)
7547
+ schema.type / schema.anyOf value has wrong shape
7548
+ manifest.version_missing (warning) add manifest_version: "0.1" at the top of the file
7549
+ manifest.version_unknown (warning) manifest_version is newer than this CLI knows; checks still ran
7550
+ manifest.shape root is not a YAML mapping
7551
+ read.not_found / read.io_error / yaml.parse_error file-level failures
7552
+
7553
+ Exit codes:
7554
+ 0 no error-severity findings (warnings + infos OK)
7555
+ 1 any error-severity finding present
7556
+
7557
+ JSON output shape (--json) — same as \`yaml validate --json\`:
7558
+ {
7559
+ "source": "<path>",
7560
+ "ok": true | false,
7561
+ "summary": { "errors": <int>, "warnings": <int>, "infos": <int> },
7562
+ "findings": [ { "severity", "rule", "message", "path"?, "line"?, "column"? } ]
7563
+ }
7564
+
7565
+ When to invoke:
7566
+ - Author time, before commit (catches the recurring miss class earliest)
7567
+ - Inside Marco / Dex agent prompts before review tokens are spent
7568
+ - In CI on any PR that touches a *-manifest.yaml file
7569
+
7570
+ Examples:
7571
+ fazemos manifest validate ./specs/tech/platform/I45-...-manifest.yaml
7572
+ fazemos manifest validate ./manifest.yaml --json | jq '.findings | map(select(.severity=="error"))'
7573
+ fazemos manifest validate ./manifest.yaml --json | jq -r '.findings[] | "\\(.severity) \\(.rule) \\(.message)"'`)
7574
+ .action((path, opts) => {
7575
+ const loaded = loadYaml(path);
7576
+ const findings = [...loaded.findings];
7577
+ if (loaded.ok) {
7578
+ findings.push(...validateManifest(loaded.value));
7579
+ }
7580
+ const summary = summarize(findings);
7581
+ if (opts.json)
7582
+ printJson(loaded.source, findings, summary);
7583
+ else
7584
+ printFindings(loaded.source, findings, summary);
7585
+ if (summary.errors > 0)
7586
+ process.exit(1);
7587
+ });
7456
7588
  // Skip auto-parse only when running under Vitest (which sets process.env.VITEST).
7457
7589
  // Tests import `program` and drive it via `program.parseAsync(...)` after mocking
7458
7590
  // `./api.js`. In every other context — direct invocation, npx tsx, OR the bin