@formigio/fazemos-cli 0.10.11 → 0.10.13
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 +229 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest/checks.d.ts +6 -0
- package/dist/manifest/checks.js +356 -0
- package/dist/manifest/checks.js.map +1 -0
- package/dist/manifest/schema.d.ts +314 -0
- package/dist/manifest/schema.js +208 -0
- package/dist/manifest/schema.js.map +1 -0
- package/dist/yaml/format.d.ts +5 -0
- package/dist/yaml/format.js +41 -0
- package/dist/yaml/format.js.map +1 -0
- package/dist/yaml/load.d.ts +28 -0
- package/dist/yaml/load.js +74 -0
- package/dist/yaml/load.js.map +1 -0
- package/package.json +5 -2
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';
|
|
@@ -2466,14 +2469,58 @@ commitments
|
|
|
2466
2469
|
process.exit(1);
|
|
2467
2470
|
}
|
|
2468
2471
|
});
|
|
2472
|
+
// [F24 §3.6 / R16-R20] cm reopen — Restore an open state on a closed
|
|
2473
|
+
// commitment. The close event stays in the audit trail (a new audit_log
|
|
2474
|
+
// row records the reopen with from_state and to_state='open'). Agents
|
|
2475
|
+
// cannot reopen — the API's permission predicate naturally excludes
|
|
2476
|
+
// service principals (requireMemberId throws when req.member.id is null).
|
|
2477
|
+
commitments
|
|
2478
|
+
.command('reopen <id>')
|
|
2479
|
+
.description('Restore an open state on a closed commitment. The close event stays in the audit trail.')
|
|
2480
|
+
.option('--reason <text>', 'Optional human-readable note for the audit log (max 500 chars)')
|
|
2481
|
+
.option('--json', 'Print the raw API response as JSON (machine-readable)')
|
|
2482
|
+
.action(async (id, opts) => {
|
|
2483
|
+
try {
|
|
2484
|
+
const body = {};
|
|
2485
|
+
if (opts.reason)
|
|
2486
|
+
body.reason = opts.reason;
|
|
2487
|
+
const data = await api('PATCH', `/api/commitments/${id}/reopen`, body);
|
|
2488
|
+
if (opts.json) {
|
|
2489
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
if (data.noop) {
|
|
2493
|
+
console.log(chalk.yellow('This commitment is already open. Nothing to reopen.'));
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
const c = data.commitment;
|
|
2497
|
+
const prev = data.previousCompletedAt;
|
|
2498
|
+
const prevFmt = prev ? new Date(prev).toISOString().slice(0, 16).replace('T', ' ') : null;
|
|
2499
|
+
const tail = prevFmt ? ` The close on ${prevFmt} stays in the audit trail.` : '';
|
|
2500
|
+
console.log(chalk.green(`Reopened. Commitment ${c.id} is open again.${tail}`));
|
|
2501
|
+
}
|
|
2502
|
+
catch (err) {
|
|
2503
|
+
console.error(chalk.red(err.message));
|
|
2504
|
+
process.exit(1);
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
// [F24 §3.4 / R13-R14] cm execute — gained `--repos` option for
|
|
2508
|
+
// commitment-source clone overrides (mirrors the top-level execute flag).
|
|
2509
|
+
// The server-side endpoint now reads agent_config.repos as a fallback
|
|
2510
|
+
// when no body.repos is supplied; this CLI flag is the operator-side
|
|
2511
|
+
// override for `fazemos cm execute -w <ws> -c <cm> --repos <repos>`.
|
|
2469
2512
|
commitments
|
|
2470
2513
|
.command('execute')
|
|
2471
2514
|
.description('Trigger an agent execution for a commitment. The API determines which agent to use based on the commitment\'s configuration. For more control, use the top-level "execute" command instead.')
|
|
2472
2515
|
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
2473
2516
|
.requiredOption('-c, --commitment <id>', 'Commitment ID (from "cm list" or "ws show" output)')
|
|
2517
|
+
.option('--repos <repos>', 'Comma-separated repo names to clone for this execution (overrides the agent\'s default agent_config.repos)', (v) => v.split(','))
|
|
2474
2518
|
.action(async (opts) => {
|
|
2475
2519
|
try {
|
|
2476
|
-
const
|
|
2520
|
+
const body = {};
|
|
2521
|
+
if (opts.repos)
|
|
2522
|
+
body.repos = opts.repos;
|
|
2523
|
+
const data = await api('POST', `/api/worksheets/${opts.worksheet}/commitments/${opts.commitment}/execute`, body);
|
|
2477
2524
|
console.log(chalk.green('Execution triggered'));
|
|
2478
2525
|
if (data.execution?.id)
|
|
2479
2526
|
console.log(` Execution ID: ${data.execution.id}`);
|
|
@@ -4718,7 +4765,8 @@ program
|
|
|
4718
4765
|
.argument('<source-id>', 'Action, commitment, or pipeline step ID')
|
|
4719
4766
|
.requiredOption('--agent <name>', 'Agent name or member ID')
|
|
4720
4767
|
.option('--source-type <type>', 'Source type (action, commitment, pipeline_step)', 'action')
|
|
4721
|
-
.option('--prompt <prompt>', '
|
|
4768
|
+
.option('--prompt <prompt>', 'Operator instructions for this execution. Appended to the agent\'s built prompt as a labeled "## Operator Instructions" section. Takes precedence over runtime auto-completion instructions per the precedence rule (see architecture doc).')
|
|
4769
|
+
.option('--no-auto-complete', 'Suppress the runtime auto-completion instruction. The agent will not be told to mark the source done. Use this for "draft, don\'t close" workflows where a human reviews and locks. Default: off. Pipeline-step source: ignored with a warning.')
|
|
4722
4770
|
.option('--repos <repos>', 'Comma-separated repo names to clone (overrides agent config)', (v) => v.split(','))
|
|
4723
4771
|
.option('--model <model>', 'Model override (e.g., opus, sonnet)')
|
|
4724
4772
|
.option('--budget <usd>', 'Max budget override in USD', parseNumber)
|
|
@@ -4729,6 +4777,22 @@ program
|
|
|
4729
4777
|
console.error(chalk.red(`Invalid source type. Must be one of: ${validTypes.join(', ')}`));
|
|
4730
4778
|
process.exit(1);
|
|
4731
4779
|
}
|
|
4780
|
+
// [F24 §3.3 / EC3 / rul_no_auto_complete_pipeline_step_ignored]
|
|
4781
|
+
// --no-auto-complete is ignored for pipeline_step sources to preserve
|
|
4782
|
+
// back-compat for shipped pipeline executions. Layer-1 defense:
|
|
4783
|
+
// CLI prints a warning and does NOT forward the flag onto
|
|
4784
|
+
// context.noAutoComplete. Layer-2 defense lives in the agent's
|
|
4785
|
+
// buildSelfReportingInstructions, which gates the noAutoComplete
|
|
4786
|
+
// check on source_type !== 'pipeline_step' regardless.
|
|
4787
|
+
//
|
|
4788
|
+
// commander.js: `--no-auto-complete` produces opts.autoComplete=false
|
|
4789
|
+
// (NOT opts.noAutoComplete=true) because of the `--no-` prefix
|
|
4790
|
+
// convention. We surface that as the boolean `forwardSuppress` below.
|
|
4791
|
+
const operatorOptedOut = opts.autoComplete === false;
|
|
4792
|
+
if (operatorOptedOut && opts.sourceType === 'pipeline_step') {
|
|
4793
|
+
console.error(chalk.yellow('Warning: --no-auto-complete is ignored for pipeline_step sources; pipeline steps always auto-complete.'));
|
|
4794
|
+
}
|
|
4795
|
+
const forwardSuppress = operatorOptedOut && opts.sourceType !== 'pipeline_step';
|
|
4732
4796
|
// Resolve agent name to member ID if not already a UUID
|
|
4733
4797
|
let agentMemberId = opts.agent;
|
|
4734
4798
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-/.test(agentMemberId)) {
|
|
@@ -4765,8 +4829,24 @@ program
|
|
|
4765
4829
|
url: `https://github.com/formigio/${r}.git`,
|
|
4766
4830
|
branch: 'main',
|
|
4767
4831
|
})),
|
|
4768
|
-
cwd: '/workspace',
|
|
4769
4832
|
};
|
|
4833
|
+
// [F24 §3.2 / D8 / rul_workspace_clone_contract] cwd defaulting per
|
|
4834
|
+
// resolved repo count. Pre-F24 the CLI hardcoded cwd='/workspace',
|
|
4835
|
+
// which is ALWAYS empty inside the runner (clones land at
|
|
4836
|
+
// /home/agent/development/<repo>). BUG20's symptom ("'.git' missing")
|
|
4837
|
+
// was just the cwd misalignment — the clone itself was already
|
|
4838
|
+
// correct. Fix:
|
|
4839
|
+
// single repo → /home/agent/development/<repo> (align with clone)
|
|
4840
|
+
// multi repo → unset, let runner pick first repo via its existing
|
|
4841
|
+
// fallback at fazemos-agent-runner.ts:1075
|
|
4842
|
+
// zero repos → /workspace (preserve chat_session and no-repo cases)
|
|
4843
|
+
if (repoNames.length === 1) {
|
|
4844
|
+
context.cwd = `/home/agent/development/${repoNames[0]}`;
|
|
4845
|
+
}
|
|
4846
|
+
else if (repoNames.length === 0) {
|
|
4847
|
+
context.cwd = '/workspace';
|
|
4848
|
+
}
|
|
4849
|
+
// multi-repo: leave context.cwd unset — runner fallback chooses
|
|
4770
4850
|
// Resolve worksheet context for actions/commitments
|
|
4771
4851
|
if (opts.sourceType === 'action' || opts.sourceType === 'commitment') {
|
|
4772
4852
|
try {
|
|
@@ -4783,6 +4863,17 @@ program
|
|
|
4783
4863
|
context.worksheetName = detail.worksheet?.name;
|
|
4784
4864
|
context.worksheetPurpose = detail.worksheet?.purpose;
|
|
4785
4865
|
context.actionDescription = match.description || match.name;
|
|
4866
|
+
// [F24 §3.3 / D3 / rul_action_completion_semantics]
|
|
4867
|
+
// Propagate the action's target_value into context so the
|
|
4868
|
+
// agent's buildSelfReportingInstructions can branch:
|
|
4869
|
+
// target_value null|1 → binary auto-complete with -v 1
|
|
4870
|
+
// target_value > 1 → emit a "non-binary target" note,
|
|
4871
|
+
// NO auto-completion command
|
|
4872
|
+
// Only meaningful for source_type='action'; we set it only
|
|
4873
|
+
// here (the action-resolution branch).
|
|
4874
|
+
if (opts.sourceType === 'action') {
|
|
4875
|
+
context.actionTargetValue = match.target_value ?? null;
|
|
4876
|
+
}
|
|
4786
4877
|
// Include linked outcomes
|
|
4787
4878
|
if (detail.outcomes?.length) {
|
|
4788
4879
|
context.outcomes = detail.outcomes.map((o) => ({
|
|
@@ -4805,6 +4896,12 @@ program
|
|
|
4805
4896
|
}
|
|
4806
4897
|
if (opts.prompt)
|
|
4807
4898
|
context.prompt = opts.prompt;
|
|
4899
|
+
// [F24 §3.3 / D2] Forward --no-auto-complete intent. The CLI guard
|
|
4900
|
+
// above suppresses this for pipeline_step (forwardSuppress is false
|
|
4901
|
+
// in that case). Agent's buildSelfReportingInstructions reads
|
|
4902
|
+
// context.noAutoComplete; absent/false → default behavior.
|
|
4903
|
+
if (forwardSuppress)
|
|
4904
|
+
context.noAutoComplete = true;
|
|
4808
4905
|
const body = {
|
|
4809
4906
|
sourceType: opts.sourceType,
|
|
4810
4907
|
sourceId,
|
|
@@ -7453,6 +7550,135 @@ docs
|
|
|
7453
7550
|
handleScopedError(err);
|
|
7454
7551
|
}
|
|
7455
7552
|
});
|
|
7553
|
+
// ── Spec-artifact validation (TOOL-1) ──────────────────────────────────
|
|
7554
|
+
//
|
|
7555
|
+
// Two top-level groups:
|
|
7556
|
+
// fazemos yaml validate <path> Generic YAML parse check
|
|
7557
|
+
// fazemos manifest validate <path> Architecture Design Manifest check
|
|
7558
|
+
//
|
|
7559
|
+
// Both support --json for structured output. Exit code 1 on errors, 0 on
|
|
7560
|
+
// warnings/info only. See CLAUDE.md "TOOL-1" for scope.
|
|
7561
|
+
const yamlGroup = program.command('yaml').description('Generic YAML file utilities. Offline; no API calls. Use as a cheap ' +
|
|
7562
|
+
'author-time or CI gate before committing a YAML file or feeding it into ' +
|
|
7563
|
+
'another tool. For Architecture Design Manifests, prefer `fazemos manifest ' +
|
|
7564
|
+
'validate` instead — it runs YAML parse plus structural and custom checks.');
|
|
7565
|
+
yamlGroup
|
|
7566
|
+
.command('validate')
|
|
7567
|
+
.description('Parse a YAML file and report parse errors with line/column. Cheap pre-commit / CI gate.')
|
|
7568
|
+
.argument('<path>', 'Path to a YAML file (.yaml or .yml). Absolute or relative to CWD.')
|
|
7569
|
+
.option('--json', 'Emit structured JSON output instead of human-readable text. See --help for shape.')
|
|
7570
|
+
.addHelpText('after', `
|
|
7571
|
+
Rule slugs (stable; safe to grep in --json output):
|
|
7572
|
+
read.not_found File at <path> does not exist
|
|
7573
|
+
read.not_a_file <path> exists but is not a regular file
|
|
7574
|
+
read.io_error Read failed (permissions, etc.)
|
|
7575
|
+
yaml.parse_error js-yaml could not parse the document
|
|
7576
|
+
|
|
7577
|
+
Exit codes:
|
|
7578
|
+
0 YAML parsed cleanly (no error findings)
|
|
7579
|
+
1 Parse or read error finding present
|
|
7580
|
+
|
|
7581
|
+
JSON output shape (--json):
|
|
7582
|
+
{
|
|
7583
|
+
"source": "<path>",
|
|
7584
|
+
"ok": true | false,
|
|
7585
|
+
"summary": { "errors": <int>, "warnings": <int>, "infos": <int> },
|
|
7586
|
+
"findings": [
|
|
7587
|
+
{ "severity": "error"|"warning"|"info",
|
|
7588
|
+
"rule": "<rule-slug>",
|
|
7589
|
+
"message": "<human-readable>",
|
|
7590
|
+
"path": "<dotted-path-into-doc>", // optional
|
|
7591
|
+
"line": <int>, // optional (parse errors)
|
|
7592
|
+
"column": <int> } // optional (parse errors)
|
|
7593
|
+
]
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7596
|
+
Examples:
|
|
7597
|
+
fazemos yaml validate ./config.yaml
|
|
7598
|
+
fazemos yaml validate ./fixture.yml --json | jq '.findings[]'`)
|
|
7599
|
+
.action((path, opts) => {
|
|
7600
|
+
const result = loadYaml(path);
|
|
7601
|
+
const summary = summarize(result.findings);
|
|
7602
|
+
if (opts.json)
|
|
7603
|
+
printJson(result.source, result.findings, summary);
|
|
7604
|
+
else
|
|
7605
|
+
printFindings(result.source, result.findings, summary);
|
|
7606
|
+
if (summary.errors > 0)
|
|
7607
|
+
process.exit(1);
|
|
7608
|
+
});
|
|
7609
|
+
const manifestGroup = program.command('manifest').description('Architecture Design Manifest validation (the YAML companion to a tech ' +
|
|
7610
|
+
'spec, conventionally at specs/tech/<area>/<feature>-manifest.yaml). ' +
|
|
7611
|
+
'Offline; no API calls. Designed to run at three points: (1) author time ' +
|
|
7612
|
+
'before commit, (2) in agent prompts (Marco / Dex) before review tokens ' +
|
|
7613
|
+
'are spent, (3) in CI on PRs touching a manifest. Catches the recurring ' +
|
|
7614
|
+
'miss class — audit-summary math, AC traceability, duplicate IDs — ' +
|
|
7615
|
+
'without a workspace-level toolchain.');
|
|
7616
|
+
manifestGroup
|
|
7617
|
+
.command('validate')
|
|
7618
|
+
.description('Validate an Architecture Design Manifest: YAML parse + structural schema + custom checks (audit math, AC traceability, duplicate IDs).')
|
|
7619
|
+
.argument('<path>', 'Path to a manifest YAML file. Convention: specs/tech/<area>/<feature>-manifest.yaml inside a workspace clone. Absolute paths also work.')
|
|
7620
|
+
.option('--json', 'Emit structured JSON output instead of human-readable text. See --help for shape.')
|
|
7621
|
+
.addHelpText('after', `
|
|
7622
|
+
What runs (in order):
|
|
7623
|
+
1. js-yaml parse (rule slug: yaml.parse_error)
|
|
7624
|
+
2. permissive structural schema (ajv) (rule slugs: schema.*)
|
|
7625
|
+
3. custom checks (rule slugs: audit.* / trace.* / ids.* / manifest.*)
|
|
7626
|
+
|
|
7627
|
+
The schema requires feature.id + feature.title. It accepts both manifest shape
|
|
7628
|
+
variants seen in the wild — entry_points as map ({added, modified, replaced,
|
|
7629
|
+
unchanged, audit_summary}) OR as a flat list, traceability values as string OR
|
|
7630
|
+
object OR array-of-rows, rules as map OR array-of-{id, ...} entries.
|
|
7631
|
+
|
|
7632
|
+
Rule slugs (stable; safe to grep in --json output):
|
|
7633
|
+
audit.sum_mismatch audit_summary.{added+modified+replaced+unchanged} ≠ total_in_this_manifest
|
|
7634
|
+
audit.count_vs_array audit_summary declared count ≠ actual entry_points.<bucket>[] length
|
|
7635
|
+
trace.ac_task_missing traceability.AC*.task references a task ID with no match in implementation.*.tasks
|
|
7636
|
+
(recurses through implementation.sequencing.* and similar nested buckets)
|
|
7637
|
+
ids.duplicate_task same task id in multiple implementation buckets
|
|
7638
|
+
ids.duplicate duplicate id in edge_cases / risks / phase_1_gate.questions
|
|
7639
|
+
helper.callers_duplicate (warning) api.internal_helper.callers lists same caller twice
|
|
7640
|
+
schema.required required field missing (feature.id, feature.title)
|
|
7641
|
+
schema.type / schema.anyOf value has wrong shape
|
|
7642
|
+
manifest.version_missing (warning) add manifest_version: "0.1" at the top of the file
|
|
7643
|
+
manifest.version_unknown (warning) manifest_version is newer than this CLI knows; checks still ran
|
|
7644
|
+
manifest.shape root is not a YAML mapping
|
|
7645
|
+
read.not_found / read.io_error / yaml.parse_error file-level failures
|
|
7646
|
+
|
|
7647
|
+
Exit codes:
|
|
7648
|
+
0 no error-severity findings (warnings + infos OK)
|
|
7649
|
+
1 any error-severity finding present
|
|
7650
|
+
|
|
7651
|
+
JSON output shape (--json) — same as \`yaml validate --json\`:
|
|
7652
|
+
{
|
|
7653
|
+
"source": "<path>",
|
|
7654
|
+
"ok": true | false,
|
|
7655
|
+
"summary": { "errors": <int>, "warnings": <int>, "infos": <int> },
|
|
7656
|
+
"findings": [ { "severity", "rule", "message", "path"?, "line"?, "column"? } ]
|
|
7657
|
+
}
|
|
7658
|
+
|
|
7659
|
+
When to invoke:
|
|
7660
|
+
- Author time, before commit (catches the recurring miss class earliest)
|
|
7661
|
+
- Inside Marco / Dex agent prompts before review tokens are spent
|
|
7662
|
+
- In CI on any PR that touches a *-manifest.yaml file
|
|
7663
|
+
|
|
7664
|
+
Examples:
|
|
7665
|
+
fazemos manifest validate ./specs/tech/platform/I45-...-manifest.yaml
|
|
7666
|
+
fazemos manifest validate ./manifest.yaml --json | jq '.findings | map(select(.severity=="error"))'
|
|
7667
|
+
fazemos manifest validate ./manifest.yaml --json | jq -r '.findings[] | "\\(.severity) \\(.rule) \\(.message)"'`)
|
|
7668
|
+
.action((path, opts) => {
|
|
7669
|
+
const loaded = loadYaml(path);
|
|
7670
|
+
const findings = [...loaded.findings];
|
|
7671
|
+
if (loaded.ok) {
|
|
7672
|
+
findings.push(...validateManifest(loaded.value));
|
|
7673
|
+
}
|
|
7674
|
+
const summary = summarize(findings);
|
|
7675
|
+
if (opts.json)
|
|
7676
|
+
printJson(loaded.source, findings, summary);
|
|
7677
|
+
else
|
|
7678
|
+
printFindings(loaded.source, findings, summary);
|
|
7679
|
+
if (summary.errors > 0)
|
|
7680
|
+
process.exit(1);
|
|
7681
|
+
});
|
|
7456
7682
|
// Skip auto-parse only when running under Vitest (which sets process.env.VITEST).
|
|
7457
7683
|
// Tests import `program` and drive it via `program.parseAsync(...)` after mocking
|
|
7458
7684
|
// `./api.js`. In every other context — direct invocation, npx tsx, OR the bin
|