@dzhechkov/harness-cli 0.3.221 → 0.3.223

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.3.221",
3
+ "version": "0.3.223",
4
4
  "description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  "@dzhechkov/skills-reverse-engineering": "^0.1.0",
56
56
  "@dzhechkov/skills-presentation-storyteller": "^0.1.0",
57
57
  "@dzhechkov/skills-website-cloner": "^0.1.0",
58
- "@dzhechkov/harness-core": "0.3.115"
58
+ "@dzhechkov/harness-core": "0.3.117"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^25.6.0",
package/src/cli.ts CHANGED
@@ -135,7 +135,17 @@ import {
135
135
  retroLessonText,
136
136
  PROCESS_SIGNATURES,
137
137
  RETRO_DOMAIN,
138
+ scanForSetup,
139
+ buildSetupPlan,
140
+ scaffoldFromSpec,
141
+ renderScaffoldPreview,
142
+ readExistingForScaffold,
143
+ assembleChallengeContext,
144
+ buildChallengeBrief,
145
+ pickAdversaryModel,
146
+ CHALLENGE_QUESTIONS,
138
147
  } from '@dzhechkov/harness-core';
148
+ import type { SetupSpec } from '@dzhechkov/harness-core';
139
149
  import type { ProvenanceMode, PackVerdict, ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow } from '@dzhechkov/harness-core';
140
150
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
141
151
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
@@ -4438,6 +4448,116 @@ async function cmdRetro(options: Map<string, string>, flags: Set<string>, cwd: s
4438
4448
  return 0;
4439
4449
  }
4440
4450
 
4451
+ /**
4452
+ * `dz feature-adr-setup` — the deterministic engine behind the `configure-feature-adr` SKILL (R5). Scaffolds
4453
+ * the project-awareness files (vision / map / testing / project-skills) so a user configures feature-adr
4454
+ * without knowing any schema.
4455
+ * --plan [--json] read-only: what exists / discoverable / missing (the "which docs, where?" answer)
4456
+ * --from-spec <spec.json> preview the scaffold (create / augment per file); the SKILL fills the spec
4457
+ * --apply with --from-spec: WRITE (create missing, AUGMENT existing — never clobber)
4458
+ */
4459
+ function cmdFeatureAdrSetup(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
4460
+ let repoRoot = cwd;
4461
+ try { repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim() || cwd; } catch { /* not git */ }
4462
+
4463
+ const specPath = options.get('from-spec');
4464
+ if (specPath === undefined) {
4465
+ // default + --plan: the read-only "which documents, and where?" answer.
4466
+ const plan = buildSetupPlan(scanForSetup(repoRoot));
4467
+ if (flags.has('json')) { write(JSON.stringify(plan, null, 2)); return 0; }
4468
+ write('feature-adr project setup — current state:');
4469
+ write(` vision.md: ${plan.exists.vision ? '✓' : '✗ missing'} manifest: ${plan.exists.manifest ? '✓' : '✗ missing'} testing.md: ${plan.exists.testing ? '✓' : '✗ missing'} project-skills.json: ${plan.exists.projectSkills ? '✓' : '✗ missing'}`);
4470
+ if (plan.discoveredPackages.length > 0) write(` discovered ${plan.discoveredPackages.length} workspace package(s) → a map can be auto-scaffolded`);
4471
+ if (plan.suggestions.length > 0) { write(' next steps:'); for (const s of plan.suggestions) write(` • ${s}`); }
4472
+ if (plan.missing.length === 0) write(' all set — feature-adr is project-aware here.');
4473
+ else write(' → the `configure-feature-adr` skill walks you through the missing docs; or pass a filled spec with --from-spec.');
4474
+ return 0;
4475
+ }
4476
+
4477
+ // --from-spec: scaffold (preview, or --apply to write).
4478
+ let spec: SetupSpec;
4479
+ try { spec = JSON.parse(readFileSync(resolve(cwd, specPath), 'utf-8')) as SetupSpec; }
4480
+ catch (e) { write(`dz feature-adr-setup: cannot read spec ${specPath}: ${e instanceof Error ? e.message : String(e)}`); return 1; }
4481
+
4482
+ const result = scaffoldFromSpec(spec, readExistingForScaffold(repoRoot));
4483
+ if (flags.has('json') && !flags.has('apply')) { write(JSON.stringify(result, null, 2)); return 0; }
4484
+ write(renderScaffoldPreview(result));
4485
+
4486
+ if (!flags.has('apply')) { write('\n(preview only — pass --apply to write)'); return 0; }
4487
+
4488
+ let wrote = 0;
4489
+ for (const f of result.files) {
4490
+ if (f.action === 'unchanged') continue; // never clobber
4491
+ const abs = resolve(repoRoot, f.path);
4492
+ // Write-boundary augment-never-clobber guard (defense in depth): a 'create' must NEVER overwrite an
4493
+ // existing file, even if the plan (built from a caller-supplied `existing`) said create. 'augment' has
4494
+ // already union-merged the prior content, so overwriting there is the merged result, not a clobber.
4495
+ if (f.action === 'create' && existsSync(abs)) {
4496
+ write(` · skipped ${f.path} — already exists (not overwritten)`);
4497
+ continue;
4498
+ }
4499
+ try {
4500
+ mkdirSync(dirname(abs), { recursive: true });
4501
+ writeFileSync(abs, f.content);
4502
+ write(` ✓ ${f.action === 'create' ? 'created' : 'augmented'} ${f.path}`);
4503
+ wrote++;
4504
+ } catch (e) { write(` ✗ ${f.path}: ${e instanceof Error ? e.message : String(e)}`); }
4505
+ }
4506
+ write(`\n↳ wrote ${wrote} file(s). Verify: dz project-skills + dz architecture --revise`);
4507
+ return 0;
4508
+ }
4509
+
4510
+ /**
4511
+ * `dz challenge` — the deterministic "cartridge" behind the `challenge-panel` adversarial plan-gate (R6).
4512
+ * Assembles a WIDE context pack (plan + vision + testing + map + degradations) and prints the fixed C1-C8
4513
+ * "break it, don't confirm it" brief + the verdict schema. It runs NO LLM and writes nothing — the
4514
+ * `challenge-panel` SKILL fires the brief at a FRESH adversary (a model ≠ the plan author) + a mandatory
4515
+ * cross-validator. ADVISE, never block.
4516
+ * --plan <plan.md> the plan under review (required)
4517
+ * --json emit the assembled context + brief as JSON
4518
+ * --context-only just the assembled context summary (what the panel will read)
4519
+ * --author <model> the plan-author model → prints the cross-family adversary to dispatch (FR-4)
4520
+ */
4521
+ function cmdChallenge(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
4522
+ let repoRoot = cwd;
4523
+ try { repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim() || cwd; } catch { /* not git */ }
4524
+
4525
+ const planPath = options.get('plan');
4526
+ if (planPath === undefined) {
4527
+ write('dz challenge: pass --plan <plan.md> (the implementation plan to challenge).');
4528
+ return 1;
4529
+ }
4530
+
4531
+ const ctx = assembleChallengeContext(repoRoot, planPath);
4532
+ if (ctx.plan === '') {
4533
+ write(`dz challenge: plan not found or empty: ${planPath}`);
4534
+ return 1;
4535
+ }
4536
+
4537
+ const adversary = pickAdversaryModel(options.get('author') ?? 'claude');
4538
+
4539
+ if (flags.has('json')) {
4540
+ write(JSON.stringify({ context: ctx, brief: buildChallengeBrief(ctx), adversary, questions: CHALLENGE_QUESTIONS }, null, 2));
4541
+ return 0;
4542
+ }
4543
+
4544
+ if (flags.has('context-only')) {
4545
+ const has = (v: string | undefined): string => (v === undefined ? '✗ (less calibration)' : `✓ ${v.length} chars`);
4546
+ write(`challenge context for ${planPath}:`);
4547
+ write(` plan: ✓ ${ctx.plan.length} chars`);
4548
+ write(` vision: ${has(ctx.vision)}`);
4549
+ write(` testing: ${has(ctx.testing)}`);
4550
+ write(` map: ${has(ctx.map)}`);
4551
+ write(` degradations:${has(ctx.degradations)}`);
4552
+ write(` → adversary: ${adversary.model} — ${adversary.note}`);
4553
+ return 0;
4554
+ }
4555
+
4556
+ write(buildChallengeBrief(ctx));
4557
+ write(`\n── dispatch (panel ≠ plan author) ──\n${adversary.model}: ${adversary.note}`);
4558
+ return 0;
4559
+ }
4560
+
4441
4561
  function cmdStats(cwd: string, write: Write): number {
4442
4562
  const baseDir = join(cwd, 'packages', '@dzhechkov');
4443
4563
  if (!existsSync(baseDir)) {
@@ -4710,6 +4830,10 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
4710
4830
  return await cmdMrRakes(options, flags, cwd, write);
4711
4831
  case 'retro':
4712
4832
  return await cmdRetro(options, flags, cwd, write);
4833
+ case 'feature-adr-setup':
4834
+ return cmdFeatureAdrSetup(options, flags, cwd, write);
4835
+ case 'challenge':
4836
+ return cmdChallenge(options, flags, cwd, write);
4713
4837
  case 'dashboard':
4714
4838
  return cmdDashboard(cwd, write);
4715
4839
  case 'roam':