etymd 0.2.2 → 0.3.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +145 -37
  3. package/dist/{approve-VC4V3OPW.js → approve-JEOEC2MV.js} +6 -4
  4. package/dist/audit-GYZ5YZ7U.js +12 -0
  5. package/dist/{brief-TZ45MNHD.js → brief-ZK7TXAC4.js} +6 -4
  6. package/dist/{chunk-MWIK6ML3.js → chunk-3NYDVB4Z.js} +123 -25
  7. package/dist/chunk-CRJHIXJH.js +5 -0
  8. package/dist/chunk-EF6BPBG6.js +22 -0
  9. package/dist/chunk-LT67FU2Y.js +59 -0
  10. package/dist/{chunk-BH37LELV.js → chunk-MKYPLNLH.js} +8 -5
  11. package/dist/{chunk-EUIHYD6F.js → chunk-MMPV67FW.js} +2 -2
  12. package/dist/{chunk-LVLZLCND.js → chunk-NFENQ3IK.js} +1 -1
  13. package/dist/{chunk-ANCCGYSH.js → chunk-NMZ3RHWW.js} +31 -59
  14. package/dist/{chunk-FZSIOCOZ.js → chunk-R74SJ7HS.js} +1 -1
  15. package/dist/{chunk-LBRNZZZF.js → chunk-TADKOW6P.js} +1 -99
  16. package/dist/{chunk-KMAJIQHC.js → chunk-V3DPLOOM.js} +7 -5
  17. package/dist/{chunk-KBF3SW3N.js → chunk-WWBF4Y7K.js} +1 -1
  18. package/dist/{chunk-IW6K5HLV.js → chunk-XTAX4YNT.js} +2 -2
  19. package/dist/chunk-YLNQQZ5F.js +102 -0
  20. package/dist/cli.js +50 -16
  21. package/dist/config-MX7OYI7F.js +4 -0
  22. package/dist/{context-GYCLR3TG.js → context-4DGXO7JR.js} +5 -3
  23. package/dist/doctor-C2RTL2QV.js +19 -0
  24. package/dist/{fleet-N3G5ZIGR.js → fleet-YZGR4UC2.js} +243 -10
  25. package/dist/gates-ILE4PXDA.js +272 -0
  26. package/dist/generate-IYCQETQB.js +4 -0
  27. package/dist/index.d.ts +113 -33
  28. package/dist/index.js +713 -257
  29. package/dist/{init-4CQOLFQG.js → init-OVLO4OBZ.js} +8 -5
  30. package/dist/ledger-TMIV45AA.js +5 -0
  31. package/dist/{scan-CYOOGJFG.js → scan-RAJUFTQR.js} +6 -4
  32. package/dist/scan-ZYZ7XDSJ.js +5 -0
  33. package/dist/screen-U7JMWZPS.js +134 -0
  34. package/package.json +2 -2
  35. package/dist/audit-LZJRJS5V.js +0 -9
  36. package/dist/doctor-6JDAHGDT.js +0 -16
  37. package/dist/gates-G5BCMRRV.js +0 -71
  38. package/dist/ledger-RHQJH5EA.js +0 -4
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ interface GitFacts {
70
70
  husky: boolean;
71
71
  /** Distinct commit authors in recent history — the solo-vs-team profile signal. */
72
72
  recentAuthors?: number;
73
- /** Ticket-key prefix inferred from recent commit subjects (e.g. "NGRE2E"). */
73
+ /** Ticket-key prefix inferred from recent commit subjects (e.g. "PROJ"). */
74
74
  ticketKey?: string;
75
75
  }
76
76
  interface HookFacts {
@@ -104,6 +104,22 @@ interface ProjectFacts {
104
104
  files: string[];
105
105
  };
106
106
  hooks: HookFacts;
107
+ /**
108
+ * This project would publish something if asked — a manifest exists and does not declare
109
+ * `private: true`.
110
+ *
111
+ * A GUESS, not a fact: npm treats a missing `private` as publishable, which is right about
112
+ * npm's semantics and wrong about a local fork nobody will ever publish. It seeds the plan;
113
+ * `gates.publishGate` in `.etymd/config.json` overrides it for good.
114
+ */
115
+ publishable: boolean;
116
+ /**
117
+ * How this project publishes, when it does. The route decides which manifest key fires a
118
+ * publish-time gate: npm runs `prepublishOnly`, while `vsce` ignores that key entirely and
119
+ * runs `vscode:prepublish` — so wiring the npm key into an extension is a gate that silently
120
+ * never runs.
121
+ */
122
+ publishRoute: "npm" | "vscode" | "none";
107
123
  artifacts: DetectedArtifact[];
108
124
  /** Optional: baselines approved by older versions predate this fact. */
109
125
  freshness?: FreshnessFacts;
@@ -159,35 +175,6 @@ declare const EXTRACTION_THRESHOLD: number;
159
175
  declare function isAlwaysAppliedCursorRule(text: string): boolean;
160
176
  declare function measureContext(root: string, perFileWords?: number): Promise<ContextBudget>;
161
177
 
162
- interface GeneratedFile {
163
- path: string;
164
- contents: string;
165
- /** Present on disk already — apply will skip or ask before overwriting. */
166
- exists: boolean;
167
- /** The existing file's content differs from what the pack would generate (hand-edited or stale). */
168
- differs?: boolean;
169
- /** Hooks need the executable bit. */
170
- executable?: boolean;
171
- label: string;
172
- }
173
- interface PlanOptions {
174
- /** Scaffold a minimal AGENTS.md (init only offers this when none exists). */
175
- agents: boolean;
176
- gates: boolean;
177
- }
178
- /** Build the file set an onboarding would write, flagging which exist and which differ. */
179
- declare function planWorkflow(root: string, facts: ProjectFacts, opts: PlanOptions): Promise<GeneratedFile[]>;
180
-
181
- interface ApplyResult {
182
- written: string[];
183
- skipped: string[];
184
- }
185
- /**
186
- * Write a planned file set. Idempotent by default: an existing file is skipped unless `overwrite`
187
- * names it — etymd never clobbers hand-authored contracts without consent.
188
- */
189
- declare function applyFiles(root: string, files: GeneratedFile[], overwrite?: Set<string>): Promise<ApplyResult>;
190
-
191
178
  declare const CONFIG_FILE: string;
192
179
  interface InstructionScope {
193
180
  /** Extra instruction files to audit beyond the auto-detected set (globs, repo-relative). */
@@ -207,10 +194,29 @@ interface StateBudgets {
207
194
  /** Chars in a state doc past which the file is a finding (session hooks truncate ~10k). */
208
195
  maxChars: number;
209
196
  }
197
+ /**
198
+ * What the generated gates should contain. Every field here is DERIVED from the repo on a first
199
+ * run and written into the plan, not asked: a wrong guess costs a slightly slow hook or a
200
+ * slightly strict audit, and both are one edit away. Prompting is reserved for the choices where
201
+ * a wrong default is expensive — trust (a permanent leak) and the publish gate (a write into
202
+ * package.json). Recording the derivation here is what makes `gates` re-runnable without
203
+ * re-deciding, and what a drift check compares against.
204
+ */
205
+ interface GateConfig {
206
+ /** Command keys the pre-push gate runs, in order. Empty = whatever the scan finds. */
207
+ commands: string[];
208
+ /** Tier the audit step fails on: risk | gap | polish. */
209
+ failOn: string;
210
+ /** Emit the publish-time screen. Unset = derive from whether the repo publishes. */
211
+ publishGate?: boolean;
212
+ /** Commands allowed into a gate despite looking like they write (an explicit override). */
213
+ allowWriting: string[];
214
+ }
210
215
  interface EtymdConfig {
211
216
  instructions: InstructionScope;
212
217
  context: ContextBudgets;
213
218
  state: StateBudgets;
219
+ gates: GateConfig;
214
220
  }
215
221
  interface LoadedConfig {
216
222
  config: EtymdConfig;
@@ -228,6 +234,43 @@ declare function configPath(root: string): string;
228
234
  */
229
235
  declare function readConfig(root: string): Promise<LoadedConfig>;
230
236
 
237
+ interface GeneratedFile {
238
+ path: string;
239
+ contents: string;
240
+ /** Present on disk already — apply will skip or ask before overwriting. */
241
+ exists: boolean;
242
+ /** The existing file's content differs from what the pack would generate (hand-edited or stale). */
243
+ differs?: boolean;
244
+ /** Hooks need the executable bit. */
245
+ executable?: boolean;
246
+ label: string;
247
+ }
248
+ interface PlanOptions {
249
+ /** Scaffold a minimal AGENTS.md (init only offers this when none exists). */
250
+ agents: boolean;
251
+ gates: boolean;
252
+ /**
253
+ * Emit the publish-time screen. Defaults to `facts.publishable` — set it explicitly to
254
+ * override the derivation (a repo that publishes by a route npm cannot see, or one that
255
+ * declines the door).
256
+ */
257
+ publishGate?: boolean;
258
+ /** Recorded gate choices; absent means derive everything from the scan. */
259
+ gateConfig?: GateConfig;
260
+ }
261
+ /** Build the file set an onboarding would write, flagging which exist and which differ. */
262
+ declare function planWorkflow(root: string, facts: ProjectFacts, opts: PlanOptions): Promise<GeneratedFile[]>;
263
+
264
+ interface ApplyResult {
265
+ written: string[];
266
+ skipped: string[];
267
+ }
268
+ /**
269
+ * Write a planned file set. Idempotent by default: an existing file is skipped unless `overwrite`
270
+ * names it — etymd never clobbers hand-authored contracts without consent.
271
+ */
272
+ declare function applyFiles(root: string, files: GeneratedFile[], overwrite?: Set<string>): Promise<ApplyResult>;
273
+
231
274
  type FindingTier = "risk" | "gap" | "polish";
232
275
  type Effort = "S" | "M" | "L";
233
276
  type Confidence = "high" | "medium" | "low";
@@ -395,6 +438,35 @@ interface AuditResult {
395
438
  declare function runAudit(root: string, opts?: AuditOptions): Promise<AuditResult>;
396
439
 
397
440
  type FleetProfile = "personal" | "corp";
441
+ /**
442
+ * How exposed an entry's history is, or could plausibly become. A SAFETY PREDICATE, not a label:
443
+ * it decides whether content screening applies, so absence must never read as the permissive
444
+ * answer. `checkManifest` treats an undeclared non-corp entry as a finding — a repo published
445
+ * without ever answering the question is exactly the case a default would hide.
446
+ *
447
+ * `public-repo` already public — outside-contribution surface.
448
+ * `public-bound` private today, plausibly public later. Screened as hard as public, because
449
+ * publishing exposes ALL history: the scrub must precede the first commit,
450
+ * not the visibility flip.
451
+ * `private` not destined to be published. An ANSWER, not the absence of one.
452
+ *
453
+ * Corp entries do not carry it — `profile: "corp"` implies the answer (machine-pinned, never
454
+ * publishable), so requiring it there would be ceremony.
455
+ */
456
+ type FleetTrust = "public-repo" | "public-bound" | "private";
457
+ /**
458
+ * Fleet-wide orientation: the one entry every other entry is guided by.
459
+ *
460
+ * Declared ONCE at the manifest level rather than as a per-entry link, because the relationship
461
+ * is constant — a per-entry field carries no information and can be forgotten, which is exactly
462
+ * how three entries came to be silently unlinked. Hoisting it makes the orphan state
463
+ * unrepresentable instead of merely detectable. A fleet with no orientation root simply omits
464
+ * the block; etymd never assumes one exists.
465
+ */
466
+ interface FleetOrientation {
467
+ /** Registered name of the orientation root. Only the root itself is exempt from being guided. */
468
+ root: string;
469
+ }
398
470
  interface FleetContract {
399
471
  state?: string;
400
472
  decisions?: string;
@@ -411,8 +483,14 @@ interface FleetEntry {
411
483
  path?: string;
412
484
  /** Remote name of the upstream this repo forks — freshness measures fork-authored commits. */
413
485
  upstream?: string;
414
- /** `"public-repo"` marks an outside-contribution surface (hygiene needles apply). */
415
- trust?: string;
486
+ /**
487
+ * How exposed this entry is — see {@link FleetTrust}. `"public-repo"` marks an
488
+ * outside-contribution surface (hygiene needles apply). Undeclared on a non-corp entry is a
489
+ * `fleet check` finding, never a silent "private".
490
+ */
491
+ trust?: FleetTrust;
492
+ /** A declared `trust` value outside the vocabulary — preserved verbatim so the check can name it. */
493
+ trustRaw?: string;
416
494
  staleAfterDays?: number;
417
495
  /** Per-entry state char-budget override (ships unset — the schema slot exists). */
418
496
  stateBudget?: number;
@@ -437,6 +515,8 @@ interface FleetManifest {
437
515
  dir: string;
438
516
  /** Resolved absolute fleet root (registry shape), when declared. */
439
517
  root?: string;
518
+ /** Fleet-wide orientation root, when the manifest declares one. Absent = this fleet has none. */
519
+ orientation?: FleetOrientation;
440
520
  machineProfile?: FleetProfile;
441
521
  corpHosts: string[];
442
522
  labels: Record<string, string>;
@@ -656,7 +736,7 @@ declare const gateIntegrityLens: Lens;
656
736
  * change meaning. Stamped into facts, baselines, and generated artifacts so drift against the
657
737
  * pack is computable and `harvest` has something to diff.
658
738
  */
659
- declare const PACK_VERSION = "2";
739
+ declare const PACK_VERSION = "3";
660
740
 
661
741
  declare const VERSION: string;
662
742
  declare const NAME: string;