etymd 0.1.0 → 0.2.1

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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { planWorkflow, applyFiles } from './chunk-FXL4554F.js';
3
- import { scanProject } from './chunk-TCWKIMCU.js';
4
- import './chunk-U33XLT5I.js';
5
- import { section, print, glyph, theme, renderPlan, git } from './chunk-WVYKYPKS.js';
2
+ import { planWorkflow, applyFiles } from './chunk-4UZBCCFJ.js';
3
+ import { scanProject } from './chunk-E2Q7WFPN.js';
4
+ import './chunk-C4Z6NXDO.js';
5
+ import { section, print, glyph, theme, renderPlan, git } from './chunk-LBRNZZZF.js';
6
6
  import { confirm, isCancel, cancel } from '@clack/prompts';
7
7
 
8
8
  async function run(opts) {
package/dist/index.d.ts CHANGED
@@ -26,9 +26,42 @@ interface DetectedArtifact {
26
26
  label: string;
27
27
  path: string;
28
28
  /** What role it plays: the single source of truth, a per-agent pointer, a skill, a gate, etc. */
29
- kind: "contract" | "state" | "adapter" | "skill" | "gate" | "map" | "sessions" | "other";
29
+ kind: "contract" | "state" | "decisions" | "adapter" | "skill" | "gate" | "map" | "sessions" | "other";
30
30
  exists: boolean;
31
31
  }
32
+ /** Committer-date freshness for one state/decisions artifact — a git fact, never mtime. */
33
+ interface ArtifactFreshness {
34
+ /** Matches `DetectedArtifact.id`. */
35
+ artifactId: string;
36
+ path: string;
37
+ /** ISO committer date (`git log -1 --format=%cI`) of the artifact's last commit. */
38
+ lastCommit: string;
39
+ /** True when the repo has commits newer than the artifact's last commit. */
40
+ commitsSince: boolean;
41
+ /**
42
+ * True when the working tree carries uncommitted changes to this artifact — the refresh is
43
+ * on disk, not yet committed. Treated fresh-now and disclosed, never flagged stale.
44
+ */
45
+ dirty?: boolean;
46
+ }
47
+ /**
48
+ * Freshness of the "this describes now" artifacts (state/decisions), judged from git alone.
49
+ * mtime is never read — checkouts, syncs, and editors rewrite it freely; the committer date is
50
+ * the only clock the repo itself vouches for.
51
+ */
52
+ interface FreshnessFacts {
53
+ /** ISO committer date of the repo's last commit. */
54
+ repoLastCommit?: string;
55
+ artifacts: ArtifactFreshness[];
56
+ /**
57
+ * Artifacts whose dates git cannot vouch for (untracked file, shallow clone, not a repo) —
58
+ * the fact is absent and the lens discloses why; absence is never itself a finding.
59
+ */
60
+ unverifiable: {
61
+ path: string;
62
+ reason: string;
63
+ }[];
64
+ }
32
65
  interface GitFacts {
33
66
  isRepo: boolean;
34
67
  branch?: string;
@@ -72,6 +105,8 @@ interface ProjectFacts {
72
105
  };
73
106
  hooks: HookFacts;
74
107
  artifacts: DetectedArtifact[];
108
+ /** Optional: baselines approved by older versions predate this fact. */
109
+ freshness?: FreshnessFacts;
75
110
  tree: {
76
111
  dirs: {
77
112
  name: string;
@@ -97,12 +132,19 @@ interface ContextBudget {
97
132
  extractionCandidates: ContextFile[];
98
133
  }
99
134
 
135
+ interface ScanOptions {
136
+ /**
137
+ * Fleet: measure repo freshness on fork-authored commits only (`HEAD --not --remotes=<name>`).
138
+ * The caller verifies the remote exists and discloses the fallback when it does not.
139
+ */
140
+ upstreamRemote?: string;
141
+ }
100
142
  /**
101
143
  * The deterministic half of a reckoning: everything knowable without an LLM. Kept pure of any
102
144
  * terminal output so it can back both the CLI and programmatic use, and so it is trivially
103
145
  * testable against a fixture directory.
104
146
  */
105
- declare function scanProject(root: string): Promise<ProjectFacts>;
147
+ declare function scanProject(root: string, opts?: ScanOptions): Promise<ProjectFacts>;
106
148
 
107
149
  /**
108
150
  * Default word count above which a single always-loaded file is worth extracting into an
@@ -159,9 +201,16 @@ interface ContextBudgets {
159
201
  /** Total always-loaded words past which the footprint itself is a finding. */
160
202
  totalWords: number;
161
203
  }
204
+ interface StateBudgets {
205
+ /** Days of commit traffic a state doc may trail the repo by before it counts as stale. */
206
+ staleAfterDays: number;
207
+ /** Chars in a state doc past which the file is a finding (session hooks truncate ~10k). */
208
+ maxChars: number;
209
+ }
162
210
  interface EtymdConfig {
163
211
  instructions: InstructionScope;
164
212
  context: ContextBudgets;
213
+ state: StateBudgets;
165
214
  }
166
215
  interface LoadedConfig {
167
216
  config: EtymdConfig;
@@ -317,6 +366,20 @@ interface AuditOptions {
317
366
  lensIds?: string[];
318
367
  /** Persist the reconciled ledger (the default; false = read-only report). */
319
368
  persistLedger?: boolean;
369
+ /**
370
+ * Fleet: the audited root is READ-ONLY — write nothing into it, not even the scan cache
371
+ * where `.etymd` already exists there. A corp worktree is never written, regardless of flags.
372
+ */
373
+ readOnlyRoot?: boolean;
374
+ /**
375
+ * Fleet: read/write the ledger at this root instead of the audited one. Corp findings persist
376
+ * under the manifest's `corp/<name>/`, never inside the corp worktree.
377
+ */
378
+ ledgerRoot?: string;
379
+ /** Fleet: per-entry state-budget overlay (registry `staleAfterDays` / `stateBudget`). */
380
+ stateBudgets?: Partial<StateBudgets>;
381
+ /** Fleet: judge repo freshness on fork-authored commits only (see `ScanOptions`). */
382
+ upstreamRemote?: string;
320
383
  }
321
384
  interface AuditResult {
322
385
  facts: ProjectFacts;
@@ -331,6 +394,149 @@ interface AuditResult {
331
394
  }
332
395
  declare function runAudit(root: string, opts?: AuditOptions): Promise<AuditResult>;
333
396
 
397
+ type FleetProfile = "personal" | "corp";
398
+ interface FleetContract {
399
+ state?: string;
400
+ decisions?: string;
401
+ goals?: string;
402
+ /** `"none"` = the contract file is legitimately absent (a declared state, not a gap). */
403
+ placement?: string;
404
+ }
405
+ interface FleetEntry {
406
+ name: string;
407
+ kind?: string;
408
+ profile: FleetProfile;
409
+ private: boolean;
410
+ /** Root-relative path as DECLARED in the tracked manifest (personal entries only). */
411
+ path?: string;
412
+ /** Remote name of the upstream this repo forks — freshness measures fork-authored commits. */
413
+ upstream?: string;
414
+ /** `"public-repo"` marks an outside-contribution surface (hygiene needles apply). */
415
+ trust?: string;
416
+ staleAfterDays?: number;
417
+ /** Per-entry state char-budget override (ships unset — the schema slot exists). */
418
+ stateBudget?: number;
419
+ contract: FleetContract;
420
+ links: Record<string, string>;
421
+ /** Absolute directory on THIS machine, when the manifest pair resolves one. */
422
+ resolvedRoot?: string;
423
+ /** Why `resolvedRoot` is absent — always disclosed, never a silent skip. */
424
+ unresolved?: string;
425
+ }
426
+ interface ManifestProblem {
427
+ kind: "parse-error" | "bad-shape" | "local-missing";
428
+ /** The file the problem lives in (manifest-relative basename). */
429
+ file: string;
430
+ detail: string;
431
+ }
432
+ interface FleetManifest {
433
+ shape: "registry" | "corpus";
434
+ /** Absolute path of the tracked manifest file. */
435
+ manifestPath: string;
436
+ /** Its directory — corp persistence roots and the sweep delta file live beside it. */
437
+ dir: string;
438
+ /** Resolved absolute fleet root (registry shape), when declared. */
439
+ root?: string;
440
+ machineProfile?: FleetProfile;
441
+ corpHosts: string[];
442
+ labels: Record<string, string>;
443
+ localDirs: Record<string, string>;
444
+ /** Absolute path where the gitignored local sibling is expected. */
445
+ localPath: string;
446
+ localPresent: boolean;
447
+ entries: FleetEntry[];
448
+ /** Structural problems — disclosed by every consumer, never defaulted away. */
449
+ problems: ManifestProblem[];
450
+ }
451
+ /** `~`-expansion is the CONSUMER's job — the tracked manifest never records a machine home. */
452
+ declare function expandTilde(p: string): string;
453
+ /**
454
+ * Load and resolve a fleet manifest (either shape) against this machine. Never throws on
455
+ * content problems — they land in `problems` / per-entry `unresolved` so every consumer can
456
+ * disclose them. Throws only when the manifest file itself cannot be read at all.
457
+ */
458
+ declare function loadFleetManifest(manifestPath: string): Promise<FleetManifest>;
459
+
460
+ declare const FLEET_LENS = "fleet-manifest";
461
+ /** The fleet `--json` schema marker — EXPERIMENTAL through 0.2.x, declared in the output. */
462
+ declare const FLEET_JSON_SCHEMA = "fleet-experimental-0.2";
463
+ interface FleetSweepOptions {
464
+ /** Restrict to these registered names (unknown names throw — a typo must not skip silently). */
465
+ only?: string[];
466
+ profile?: "personal" | "corp";
467
+ /** Truth lenses only per repo (the doctor subset). */
468
+ kind?: LensKind;
469
+ /**
470
+ * Persist per-repo ledgers — ONLY for personal-profile entries that already carry `.etymd`.
471
+ * The sweep never creates `.etymd` anywhere, and corp worktrees are never written at all.
472
+ */
473
+ persistLedgers?: boolean;
474
+ }
475
+ interface FleetProjectSweep {
476
+ name: string;
477
+ profile: "personal" | "corp";
478
+ kind?: string;
479
+ resolvedRoot?: string;
480
+ /** Why this entry could not be audited — mirrored into `outOfScope`. */
481
+ unresolved?: string;
482
+ staleAfterDays: number;
483
+ /** Days the newest state artifact trails the repo clock; null = nothing datable. */
484
+ stateAgeDays: number | null;
485
+ counts: Record<FindingTier, number>;
486
+ /** Ranked, ledger-quieted findings from this repo's audit. */
487
+ findings: Finding[];
488
+ disclosures: string[];
489
+ }
490
+ interface FleetSweepResult {
491
+ schema: typeof FLEET_JSON_SCHEMA;
492
+ manifest: string;
493
+ sweptAt: string;
494
+ projects: FleetProjectSweep[];
495
+ /** Fleet-scope wall findings (`fleet-manifest`) — not ledger-quietable in 0.2. */
496
+ wall: Finding[];
497
+ wallDisclosures: string[];
498
+ /** Entry names that could not be audited — named here, never silently dropped. */
499
+ outOfScope: string[];
500
+ /** Manifest problems, verbatim — disclosed by every renderer, never defaulted away. */
501
+ problems: string[];
502
+ /** Defect classes open in ≥2 projects — the class-fix candidates (see recurringClasses()). */
503
+ recurringClasses: RecurringClass[];
504
+ }
505
+ interface RecurringClass {
506
+ /** The `<lens>/<class>` id prefix — minted by the engine's lenses, identical across repos. */
507
+ classId: string;
508
+ tier: FindingTier;
509
+ /** Project names (corp entries by alias) where the class is currently open. */
510
+ projects: string[];
511
+ }
512
+ /**
513
+ * Where a corp entry's audit state persists: beside the manifest, never in the worktree.
514
+ * Belt-and-braces to the loader's safe-name rule: a name that resolves anywhere but directly
515
+ * under `<dir>/corp/` is refused — the manifest can lie, and a lie must never steer a write.
516
+ */
517
+ declare function corpPersistenceRoot(manifest: FleetManifest, name: string): string;
518
+ /**
519
+ * Validate the manifest pair itself: parse problems, dangling mappings (the live ghost-entry
520
+ * class), duplicate names, privacy leaks, dead links, machine paths. Pure manifest truth —
521
+ * no lenses run, no repo is audited, nothing is written.
522
+ */
523
+ declare function checkManifest(manifest: FleetManifest): Promise<{
524
+ findings: Finding[];
525
+ disclosures: string[];
526
+ }>;
527
+ /** All fleet-scope wall checks. Each check that cannot run says so — undetermined, not clean. */
528
+ declare function collectWallFindings(manifest: FleetManifest): Promise<{
529
+ findings: Finding[];
530
+ disclosures: string[];
531
+ }>;
532
+ /**
533
+ * The fleet sweep: one read-only audit per resolved entry + the fleet-scope wall checks.
534
+ * Invariants (each pinned by test): never creates `.etymd` anywhere; never writes into a corp
535
+ * worktree regardless of flags or a stray `.etymd` there; unresolvable entries land in
536
+ * `outOfScope` with a disclosure, never a silent skip.
537
+ */
538
+ declare function sweepFleet(manifest: FleetManifest, opts?: FleetSweepOptions): Promise<FleetSweepResult>;
539
+
334
540
  /**
335
541
  * The truth lens: does what the instruction files CLAIM still hold against the actual repo?
336
542
  * Commands must exist as scripts, paths must exist on disk, files must agree on the package
@@ -391,6 +597,20 @@ declare const TOTAL_BUDGET_WORDS: number;
391
597
  */
392
598
  declare const contextEconomyLens: Lens;
393
599
 
600
+ /**
601
+ * The marker that opts a decisions file into per-entry format checks. Forward-only by design:
602
+ * mandatory-field checks apply to files that declared the format, never retroactively to
603
+ * pre-existing records — old decisions are history, not defects.
604
+ */
605
+ declare const DECISIONS_FORMAT_MARKER = "<!-- decisions-format: 1 -->";
606
+ /**
607
+ * Freshness lens: does the layer that claims "this describes now" still describe now? Staleness
608
+ * is RELATIVE — a state doc is stale only when the repo moved past it, so a dormant repo's old
609
+ * state is current by definition and produces zero findings. Every date is a git committer date
610
+ * (never mtime); everything git cannot vouch for is disclosed, never flagged.
611
+ */
612
+ declare const stateFreshnessLens: Lens;
613
+
394
614
  type GateTool = "typecheck" | "lint" | "format-check" | "format-write" | "test" | "coverage" | "e2e" | "sonar" | "codecov" | "commitlint" | "size" | "chromatic";
395
615
  interface CiJobGate {
396
616
  job: string;
@@ -441,4 +661,4 @@ declare const PACK_VERSION = "2";
441
661
  declare const VERSION: string;
442
662
  declare const NAME: string;
443
663
 
444
- export { type ApplyResult, type AuditOptions, type AuditResult, type Baseline, CONFIG_FILE, type ContextBudget, type ContextBudgets, type ContextFile, DEFAULT_CONFIG, type DetectedArtifact, type DiscoveredCommands, EXTRACTION_THRESHOLD, type EtymdConfig, type Finding, type GateInventory, type GateTool, type GeneratedFile, type HookFacts, type InstructionFileSet, type InstructionScope, LENSES, type Ledger, type LedgerDiff, type LedgerEntry, type Lens, type LensReport, type LoadedConfig, NAME, PACK_VERSION, type PackageManager, type PathClaims, type PlanOptions, type ProjectFacts, TOTAL_BUDGET_WORDS, VERSION, type WorkflowProfile, type WorkspaceKind, applyFiles, baselineCarriesMachinePath, baselinePath, buildGateInventory, cacheFactsPath, configPath, contextEconomyLens, deriveProfile, extractCommandClaims, extractPathClaims, gateIntegrityLens, instructionTruthLens, isAlwaysAppliedCursorRule, listInstructionFiles, measureContext, planWorkflow, rankFindings, readBaseline, readCachedFacts, readConfig, readLedger, reconcileLedger, runAudit, scanProject, withoutMachinePath, writeBaseline, writeCachedFacts, writeLedger };
664
+ export { type ApplyResult, type ArtifactFreshness, type AuditOptions, type AuditResult, type Baseline, CONFIG_FILE, type ContextBudget, type ContextBudgets, type ContextFile, DECISIONS_FORMAT_MARKER, DEFAULT_CONFIG, type DetectedArtifact, type DiscoveredCommands, EXTRACTION_THRESHOLD, type EtymdConfig, FLEET_JSON_SCHEMA, FLEET_LENS, type Finding, type FleetContract, type FleetEntry, type FleetManifest, type FleetProfile, type FleetProjectSweep, type FleetSweepOptions, type FleetSweepResult, type FreshnessFacts, type GateInventory, type GateTool, type GeneratedFile, type HookFacts, type InstructionFileSet, type InstructionScope, LENSES, type Ledger, type LedgerDiff, type LedgerEntry, type Lens, type LensReport, type LoadedConfig, type ManifestProblem, NAME, PACK_VERSION, type PackageManager, type PathClaims, type PlanOptions, type ProjectFacts, type StateBudgets, TOTAL_BUDGET_WORDS, VERSION, type WorkflowProfile, type WorkspaceKind, applyFiles, baselineCarriesMachinePath, baselinePath, buildGateInventory, cacheFactsPath, checkManifest, collectWallFindings, configPath, contextEconomyLens, corpPersistenceRoot, deriveProfile, expandTilde, extractCommandClaims, extractPathClaims, gateIntegrityLens, instructionTruthLens, isAlwaysAppliedCursorRule, listInstructionFiles, loadFleetManifest, measureContext, planWorkflow, rankFindings, readBaseline, readCachedFacts, readConfig, readLedger, reconcileLedger, runAudit, scanProject, stateFreshnessLens, sweepFleet, withoutMachinePath, writeBaseline, writeCachedFacts, writeLedger };