@momentiq/dark-factory-cli 2.0.0 → 2.1.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 (49) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +18 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/onboard.d.ts +6 -0
  5. package/dist/commands/onboard.d.ts.map +1 -0
  6. package/dist/commands/onboard.js +111 -0
  7. package/dist/commands/onboard.js.map +1 -0
  8. package/dist/cycle-doc-validator/validate_cycle_doc.py +131 -60
  9. package/dist/onboard/analyze.d.ts +7 -0
  10. package/dist/onboard/analyze.d.ts.map +1 -0
  11. package/dist/onboard/analyze.js +86 -0
  12. package/dist/onboard/analyze.js.map +1 -0
  13. package/dist/onboard/analyzer.d.ts +15 -0
  14. package/dist/onboard/analyzer.d.ts.map +1 -0
  15. package/dist/onboard/analyzer.js +52 -0
  16. package/dist/onboard/analyzer.js.map +1 -0
  17. package/dist/onboard/analyzers/ci.d.ts +3 -0
  18. package/dist/onboard/analyzers/ci.d.ts.map +1 -0
  19. package/dist/onboard/analyzers/ci.js +112 -0
  20. package/dist/onboard/analyzers/ci.js.map +1 -0
  21. package/dist/onboard/analyzers/docs.d.ts +3 -0
  22. package/dist/onboard/analyzers/docs.d.ts.map +1 -0
  23. package/dist/onboard/analyzers/docs.js +151 -0
  24. package/dist/onboard/analyzers/docs.js.map +1 -0
  25. package/dist/onboard/analyzers/git.d.ts +3 -0
  26. package/dist/onboard/analyzers/git.d.ts.map +1 -0
  27. package/dist/onboard/analyzers/git.js +87 -0
  28. package/dist/onboard/analyzers/git.js.map +1 -0
  29. package/dist/onboard/analyzers/lockfile.d.ts +3 -0
  30. package/dist/onboard/analyzers/lockfile.d.ts.map +1 -0
  31. package/dist/onboard/analyzers/lockfile.js +285 -0
  32. package/dist/onboard/analyzers/lockfile.js.map +1 -0
  33. package/dist/onboard/analyzers/manifest.d.ts +3 -0
  34. package/dist/onboard/analyzers/manifest.d.ts.map +1 -0
  35. package/dist/onboard/analyzers/manifest.js +228 -0
  36. package/dist/onboard/analyzers/manifest.js.map +1 -0
  37. package/dist/onboard/analyzers/tree.d.ts +3 -0
  38. package/dist/onboard/analyzers/tree.d.ts.map +1 -0
  39. package/dist/onboard/analyzers/tree.js +196 -0
  40. package/dist/onboard/analyzers/tree.js.map +1 -0
  41. package/dist/onboard/fixtures/replay-git-history.d.ts +20 -0
  42. package/dist/onboard/fixtures/replay-git-history.d.ts.map +1 -0
  43. package/dist/onboard/fixtures/replay-git-history.js +91 -0
  44. package/dist/onboard/fixtures/replay-git-history.js.map +1 -0
  45. package/dist/onboard/schema.d.ts +545 -0
  46. package/dist/onboard/schema.d.ts.map +1 -0
  47. package/dist/onboard/schema.js +142 -0
  48. package/dist/onboard/schema.js.map +1 -0
  49. package/package.json +5 -1
@@ -134,7 +134,17 @@ def _resolve_repo_root() -> Path:
134
134
 
135
135
 
136
136
  REPO_ROOT = _resolve_repo_root()
137
+ # Cycle-doc lookup paths in priority order. Legacy `docs/roadmap/cycles/` is
138
+ # tried first for backward compatibility with sage3c / dark-factory-platform;
139
+ # `docs/cycles/` is the newer dark-factory-dashboard convention (PR #105
140
+ # moved it there to align with the eventual upstream convention). Consumers
141
+ # that have moved to the new path get a working validator without
142
+ # configuration; consumers still on the legacy path are unchanged.
143
+ CYCLE_DOC_DIRS: tuple[str, ...] = ("docs/roadmap/cycles", "docs/cycles")
137
144
  CYCLES_DIR = REPO_ROOT / "docs" / "roadmap" / "cycles"
145
+ # Glob and the singular `CYCLES_DIR` retained for backward compatibility with
146
+ # downstream tooling that imports them. New code should prefer `CYCLE_DOC_DIRS`
147
+ # + `_is_cycle_doc_path()` which check both layouts.
138
148
  CYCLE_DOC_GLOB = "docs/roadmap/cycles/cycle*.md"
139
149
 
140
150
  # Trailers we recognize. ``git-interpret-trailers`` defines a trailer as
@@ -238,14 +248,27 @@ def normalize_cycle_id(raw: str) -> str | None:
238
248
 
239
249
 
240
250
  def find_cycle_doc(cycle_id: str) -> CycleDoc | None:
241
- """Locate ``docs/roadmap/cycles/cycle<id>*.md`` and read its status."""
242
- matches = sorted(CYCLES_DIR.glob(f"cycle{cycle_id}-*.md")) + sorted(
243
- CYCLES_DIR.glob(f"cycle{cycle_id}.md")
244
- )
251
+ """Locate ``<cycle_dir>/cycle<id>*.md`` and read its status.
252
+
253
+ Searches every candidate dir in ``CYCLE_DOC_DIRS`` (legacy
254
+ ``docs/roadmap/cycles/`` first, then ``docs/cycles/``) so consumers
255
+ that have moved get a working lookup without configuration. If the
256
+ same cycle exists in both dirs (transitional state), prefers the
257
+ longer-named match across all hits — same disambiguation rule as
258
+ before, just over a wider candidate set.
259
+ """
260
+ matches: list[Path] = []
261
+ for cycle_dir in CYCLE_DOC_DIRS:
262
+ dir_path = REPO_ROOT / cycle_dir
263
+ if not dir_path.exists():
264
+ continue
265
+ matches.extend(sorted(dir_path.glob(f"cycle{cycle_id}-*.md")))
266
+ matches.extend(sorted(dir_path.glob(f"cycle{cycle_id}.md")))
245
267
  if not matches:
246
268
  return None
247
- # If multiple matches exist (legacy ``cycle318.md`` + new ``cycle318-foo.md``),
248
- # prefer the one with the longer name (more descriptive slug).
269
+ # If multiple matches exist (legacy ``cycle318.md`` + new ``cycle318-foo.md``,
270
+ # or one in each layout), prefer the one with the longer name (more
271
+ # descriptive slug).
249
272
  path = max(matches, key=lambda p: len(p.name))
250
273
  status, superseded_by = read_cycle_frontmatter(path)
251
274
  return CycleDoc(path=path, status=status, superseded_by=superseded_by)
@@ -332,50 +355,89 @@ def base_cycle_doc(
332
355
  if gh_token:
333
356
  env["GH_TOKEN"] = gh_token
334
357
 
335
- try:
336
- listing_result = subprocess.run(
337
- [
338
- "gh",
339
- "api",
340
- f"repos/{repo}/contents/docs/roadmap/cycles?ref={base_ref}",
341
- ],
342
- check=True,
343
- capture_output=True,
344
- text=True,
345
- env=env,
346
- timeout=60,
358
+ # Collect candidates across EVERY candidate cycle-doc dir on the base
359
+ # ref — DON'T break on the first successful listing. A consumer can
360
+ # have BOTH layouts present (transitional state — e.g. dark-factory-
361
+ # dashboard pre-#105 had cycle1-5 at legacy + cycle6 at the new path
362
+ # for one PR's worth of overlap), and a legacy dir that exists but
363
+ # lacks the cited cycle (sage3c with cycle331.x at legacy + a new
364
+ # cycle 6 at the new path) must fall through to the next dir. Per-dir
365
+ # 404 is the normal "consumer has not moved (or has moved fully) to
366
+ # this layout" signal; only fail if EVERY dir 404s or any dir hits a
367
+ # non-404 error.
368
+ pattern = re.compile(rf"^cycle{re.escape(cycle_id)}(?:-.+)?\.md$")
369
+ candidates: list[dict] = []
370
+ dirs_listed: list[str] = []
371
+ last_404: BaseCycleDocFetchError | None = None
372
+ for cycle_dir in CYCLE_DOC_DIRS:
373
+ try:
374
+ listing_result = subprocess.run(
375
+ [
376
+ "gh",
377
+ "api",
378
+ f"repos/{repo}/contents/{cycle_dir}?ref={base_ref}",
379
+ ],
380
+ check=True,
381
+ capture_output=True,
382
+ text=True,
383
+ env=env,
384
+ timeout=60,
385
+ )
386
+ except FileNotFoundError as exc:
387
+ # gh CLI itself missing — environmental, fail loud regardless
388
+ # of which cycle_dir we were probing.
389
+ raise BaseCycleDocFetchError(
390
+ "gh CLI not on PATH; cannot read cycle docs from base ref."
391
+ ) from exc
392
+ except subprocess.CalledProcessError as exc:
393
+ # Treat 404 (or "not found") as "this dir doesn't exist on
394
+ # base ref; keep trying the others". Any other non-zero exit
395
+ # (auth, rate-limit, server error) escapes immediately —
396
+ # don't silently swallow a real failure as a missing-dir.
397
+ if _is_gh_not_found_error(exc):
398
+ last_404 = BaseCycleDocFetchError(
399
+ f"gh api repos/{repo}/contents/{cycle_dir} returned 404 "
400
+ f"(exit {exc.returncode}): {(exc.stderr or '').strip()}"
401
+ )
402
+ continue
403
+ raise BaseCycleDocFetchError(
404
+ f"gh api repos/{repo}/contents/{cycle_dir} failed "
405
+ f"(exit {exc.returncode}): {(exc.stderr or '').strip()}"
406
+ ) from exc
407
+ except subprocess.TimeoutExpired as exc:
408
+ raise BaseCycleDocFetchError(
409
+ f"gh api repos/{repo}/contents/{cycle_dir} timed out after {exc.timeout}s"
410
+ ) from exc
411
+
412
+ dirs_listed.append(cycle_dir)
413
+ try:
414
+ entries = json.loads(listing_result.stdout)
415
+ except json.JSONDecodeError as exc:
416
+ raise BaseCycleDocFetchError(
417
+ f"GitHub contents API returned malformed JSON for {cycle_dir} listing."
418
+ ) from exc
419
+
420
+ candidates.extend(
421
+ entry
422
+ for entry in entries
423
+ if isinstance(entry, dict)
424
+ and entry.get("type") == "file"
425
+ and isinstance(entry.get("name"), str)
426
+ and pattern.match(entry["name"])
347
427
  )
348
- except FileNotFoundError as exc:
349
- raise BaseCycleDocFetchError(
350
- "gh CLI not on PATH; cannot read cycle docs from base ref."
351
- ) from exc
352
- except subprocess.CalledProcessError as exc:
353
- raise BaseCycleDocFetchError(
354
- f"gh api repos/{repo}/contents/docs/roadmap/cycles failed "
355
- f"(exit {exc.returncode}): {exc.stderr.strip()}"
356
- ) from exc
357
- except subprocess.TimeoutExpired as exc:
358
- raise BaseCycleDocFetchError(
359
- f"gh api repos/{repo}/contents/docs/roadmap/cycles timed out after {exc.timeout}s"
360
- ) from exc
361
428
 
362
- try:
363
- entries = json.loads(listing_result.stdout)
364
- except json.JSONDecodeError as exc:
365
- raise BaseCycleDocFetchError(
366
- "GitHub contents API returned malformed JSON for cycle docs listing."
367
- ) from exc
429
+ if not dirs_listed:
430
+ # EVERY candidate dir 404'd — no cycle docs anywhere on base ref.
431
+ # Surface the last 404 so consumers see a concrete actionable error
432
+ # naming what was tried.
433
+ raise last_404 or BaseCycleDocFetchError(
434
+ "no candidate cycle-doc directory found on base ref "
435
+ f"(tried: {', '.join(CYCLE_DOC_DIRS)})"
436
+ )
368
437
 
369
- pattern = re.compile(rf"^cycle{re.escape(cycle_id)}(?:-.+)?\.md$")
370
- candidates = [
371
- entry
372
- for entry in entries
373
- if isinstance(entry, dict)
374
- and entry.get("type") == "file"
375
- and isinstance(entry.get("name"), str)
376
- and pattern.match(entry["name"])
377
- ]
378
438
  if not candidates:
439
+ # At least one dir existed but doesn't hold the cited cycle —
440
+ # treat as "new cycle, OK to proceed" (the doc lands in this PR).
379
441
  return None
380
442
 
381
443
  chosen = max(candidates, key=lambda entry: len(entry["name"]))
@@ -657,18 +719,22 @@ def _parse_paginated_commit_messages(raw: str) -> str:
657
719
 
658
720
 
659
721
  def _is_cycle_doc_path(path: str) -> bool:
660
- """Match ``docs/roadmap/cycles/cycle<id>(-<slug>)?.md``.
722
+ """Match ``<cycle_dir>/cycle<id>(-<slug>)?.md`` under any layout.
661
723
 
662
- The classifier counts only files whose basename starts with
663
- ``cycle`` followed by an id character. Sibling files like
664
- ``docs/roadmap/cycles/README.md`` are index/meta content and do
665
- NOT trigger the plan-PR contract.
724
+ Accepts both legacy ``docs/roadmap/cycles/`` and the newer
725
+ ``docs/cycles/`` per ``CYCLE_DOC_DIRS``. The classifier counts only
726
+ files whose basename starts with ``cycle`` followed by an id
727
+ character. Sibling files like ``<cycle_dir>/README.md`` are
728
+ index/meta content and do NOT trigger the plan-PR contract.
666
729
  """
667
- prefix = "docs/roadmap/cycles/cycle"
668
- if not path.startswith(prefix) or not path.endswith(".md"):
730
+ if not path.endswith(".md"):
669
731
  return False
670
- next_char = path[len(prefix) : len(prefix) + 1]
671
- return next_char.isdigit()
732
+ for cycle_dir in CYCLE_DOC_DIRS:
733
+ prefix = f"{cycle_dir}/cycle"
734
+ if path.startswith(prefix):
735
+ next_char = path[len(prefix) : len(prefix) + 1]
736
+ return next_char.isdigit()
737
+ return False
672
738
 
673
739
 
674
740
  def is_plan_pr(
@@ -731,7 +797,9 @@ def status_completion_in_diff(diff: str) -> list[str]:
731
797
  in_frontmatter = False
732
798
  seen_completed_in_frontmatter = False
733
799
  continue
734
- if not current_file or not current_file.startswith("docs/roadmap/cycles/"):
800
+ if not current_file or not any(
801
+ current_file.startswith(f"{d}/") for d in CYCLE_DOC_DIRS
802
+ ):
735
803
  continue
736
804
  # Naive frontmatter tracking inside the diff hunk: ``---`` delimiters
737
805
  # toggle the flag. False positives are acceptable here — the goal
@@ -789,7 +857,7 @@ def cycle_docs_transitioned_to_terminal(
789
857
  cycle_paths = [
790
858
  p
791
859
  for p in changed_files
792
- if p.startswith("docs/roadmap/cycles/cycle") and p.endswith(".md")
860
+ if any(p.startswith(f"{d}/cycle") for d in CYCLE_DOC_DIRS) and p.endswith(".md")
793
861
  ]
794
862
  if not cycle_paths:
795
863
  return []
@@ -1030,8 +1098,9 @@ def validate(
1030
1098
  if doc is None:
1031
1099
  errors.append(
1032
1100
  f"[cycle-doc] FAIL ({pr_type}): cycle `{cycle_id}` not found in "
1033
- f"`docs/roadmap/cycles/`. Either fix the trailer or open a plan "
1034
- "PR that creates the cycle doc first."
1101
+ f"any of: {', '.join(f'`{d}/`' for d in CYCLE_DOC_DIRS)}. "
1102
+ "Either fix the trailer or open a plan PR that creates the "
1103
+ "cycle doc first."
1035
1104
  )
1036
1105
  return errors
1037
1106
 
@@ -1056,7 +1125,9 @@ def validate(
1056
1125
  # PR that doesn't touch its cycle's doc is mis-labelled.
1057
1126
  assert doc is not None # plan PR without cycle_id already returned
1058
1127
  cycle_paths = [
1059
- f for f in changed_files if f.startswith("docs/roadmap/cycles/")
1128
+ f
1129
+ for f in changed_files
1130
+ if any(f.startswith(f"{d}/") for d in CYCLE_DOC_DIRS)
1060
1131
  ]
1061
1132
  doc_relative = doc.path.relative_to(REPO_ROOT).as_posix()
1062
1133
  if doc_relative not in cycle_paths:
@@ -0,0 +1,7 @@
1
+ import { type Analyzer } from "./analyzer.js";
2
+ import { type RepoAnalysis } from "./schema.js";
3
+ declare const ALL_ANALYZERS: Analyzer[];
4
+ export declare const REPO_ANALYSIS_BYTE_BUDGET = 16384;
5
+ export declare function analyze(rootDir: string, analyzers?: Analyzer[]): Promise<RepoAnalysis>;
6
+ export { ALL_ANALYZERS };
7
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/onboard/analyze.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAQrB,QAAA,MAAM,aAAa,YAOlB,CAAC;AAOF,eAAO,MAAM,yBAAyB,QAAS,CAAC;AA8BhD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,QAAQ,EAAkB,GACpC,OAAO,CAAC,YAAY,CAAC,CA8BvB;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,86 @@
1
+ // analyze() orchestrator — cycle 15 Phase A.
2
+ //
3
+ // Pulls the 6 domain analyzers together, surfaces failures as a first-class
4
+ // `analyzerErrors` field (never silently dropped), enforces the 16 KB
5
+ // serialized-size budget as a final backstop, returns a Zod-validated
6
+ // RepoAnalysis.
7
+ import { runAnalyzers } from "./analyzer.js";
8
+ import { RepoAnalysisSchema, AGENT_CONTEXT_SCHEMA_VERSION, } from "./schema.js";
9
+ import { manifestAnalyzer } from "./analyzers/manifest.js";
10
+ import { lockfileAnalyzer } from "./analyzers/lockfile.js";
11
+ import { ciAnalyzer } from "./analyzers/ci.js";
12
+ import { treeAnalyzer } from "./analyzers/tree.js";
13
+ import { gitAnalyzer } from "./analyzers/git.js";
14
+ import { docsAnalyzer } from "./analyzers/docs.js";
15
+ const ALL_ANALYZERS = [
16
+ manifestAnalyzer,
17
+ lockfileAnalyzer,
18
+ ciAnalyzer,
19
+ treeAnalyzer,
20
+ gitAnalyzer,
21
+ docsAnalyzer,
22
+ ];
23
+ // The 16 KB serialized-size budget is a hard contract (cycle 15 D2 line 154,
24
+ // exit criterion "RepoAnalysis JSON of bounded size (≤ 16 KB)"). The per-array
25
+ // .max() caps on the Zod schema prevent the common overflow paths; this final
26
+ // enforcement is the backstop for unforeseen growth (giant headings, very long
27
+ // canonicalName, etc.) so a partial silent-truncation can never reach Phase B.
28
+ export const REPO_ANALYSIS_BYTE_BUDGET = 16_384;
29
+ function emptyBase(rootDir) {
30
+ return {
31
+ schemaVersion: AGENT_CONTEXT_SCHEMA_VERSION,
32
+ repoRoot: rootDir,
33
+ canonicalName: "",
34
+ stacks: [],
35
+ services: [],
36
+ dependencies: [],
37
+ ci: { workflows: [], deployStory: null },
38
+ tree: { topLevelDirs: [], languageBreakdown: {}, testDirs: [], fileCount: 0 },
39
+ git: {
40
+ recentCommitConventions: { conventional: false, cycleReferenced: false },
41
+ defaultBranch: "main",
42
+ },
43
+ docs: {
44
+ existing: [],
45
+ hasClaudeMd: false,
46
+ hasAgentsMd: false,
47
+ agentContextSetPresent: false,
48
+ claudeMd: null,
49
+ agentsMd: null,
50
+ },
51
+ dfPresence: { hooks: false, configJson: false, prWorkflow: false, cliPin: null },
52
+ decisions: [],
53
+ analyzerErrors: [],
54
+ };
55
+ }
56
+ export async function analyze(rootDir, analyzers = ALL_ANALYZERS) {
57
+ const merged = await runAnalyzers(rootDir, analyzers);
58
+ // Surface analyzer failures as a first-class schema field; never drop them.
59
+ // The orchestrator does NOT throw on per-analyzer errors — partial-result
60
+ // reporting is the contract — but the field's presence is the loud signal
61
+ // Phase B and CLI consumers branch on.
62
+ const { __analyzerErrors, ...rest } = merged;
63
+ const base = emptyBase(rootDir);
64
+ const combined = {
65
+ ...base,
66
+ ...rest,
67
+ // Each top-level object field needs explicit merge so partial analyzers
68
+ // don't blow away the defaults from emptyBase.
69
+ ci: { ...base.ci, ...(rest.ci ?? {}) },
70
+ tree: { ...base.tree, ...(rest.tree ?? {}) },
71
+ git: { ...base.git, ...(rest.git ?? {}) },
72
+ docs: { ...base.docs, ...(rest.docs ?? {}) },
73
+ dfPresence: { ...base.dfPresence, ...(rest.dfPresence ?? {}) },
74
+ analyzerErrors: __analyzerErrors ?? [],
75
+ };
76
+ const parsed = RepoAnalysisSchema.parse(combined);
77
+ const json = JSON.stringify(parsed);
78
+ if (json.length > REPO_ANALYSIS_BYTE_BUDGET) {
79
+ throw new Error(`RepoAnalysis exceeds ${REPO_ANALYSIS_BYTE_BUDGET}-byte budget: produced ${json.length} bytes. ` +
80
+ "Likely cause: oversized headings/decisions/dependencies; rerun against a bounded subset or " +
81
+ "tighten analyzer caps. (cycle 15 D2 / exit criterion)");
82
+ }
83
+ return parsed;
84
+ }
85
+ export { ALL_ANALYZERS };
86
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/onboard/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { RepoAnalysis, AnalyzerError } from "./schema.js";
2
+ export interface Analyzer {
3
+ name: string;
4
+ /**
5
+ * Return `null` to opt out (e.g. no `package.json` → manifest analyzer's
6
+ * JS branch skips). Return a `Partial<RepoAnalysis>` to contribute fields;
7
+ * the orchestrator merges all contributions.
8
+ */
9
+ detect(rootDir: string): Promise<Partial<RepoAnalysis> | null>;
10
+ }
11
+ export type MergedAnalysis = Partial<RepoAnalysis> & {
12
+ __analyzerErrors?: AnalyzerError[];
13
+ };
14
+ export declare function runAnalyzers(rootDir: string, analyzers: Analyzer[]): Promise<MergedAnalysis>;
15
+ //# sourceMappingURL=analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/onboard/analyzer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;CAChE;AAKD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG;IACnD,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;CACpC,CAAC;AAEF,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC,cAAc,CAAC,CA+BzB"}
@@ -0,0 +1,52 @@
1
+ export async function runAnalyzers(rootDir, analyzers) {
2
+ const results = await Promise.all(analyzers.map(async (a) => {
3
+ try {
4
+ return {
5
+ name: a.name,
6
+ value: await a.detect(rootDir),
7
+ error: null,
8
+ };
9
+ }
10
+ catch (e) {
11
+ return {
12
+ name: a.name,
13
+ value: null,
14
+ error: e instanceof Error ? e.message : String(e),
15
+ };
16
+ }
17
+ }));
18
+ const merged = {};
19
+ const errors = [];
20
+ for (const r of results) {
21
+ if (r.error !== null) {
22
+ errors.push({ name: r.name, error: r.error });
23
+ continue;
24
+ }
25
+ if (r.value === null)
26
+ continue;
27
+ mergeInto(merged, r.value);
28
+ }
29
+ if (errors.length > 0)
30
+ merged.__analyzerErrors = errors;
31
+ return merged;
32
+ }
33
+ function mergeInto(target, source) {
34
+ for (const [key, value] of Object.entries(source)) {
35
+ if (value === undefined)
36
+ continue;
37
+ if (Array.isArray(value)) {
38
+ const existing = target[key] ?? [];
39
+ target[key] = [...existing, ...value];
40
+ }
41
+ else if (typeof value === "object" &&
42
+ value !== null &&
43
+ !Array.isArray(value)) {
44
+ const existing = target[key] ?? {};
45
+ target[key] = { ...existing, ...value };
46
+ }
47
+ else {
48
+ target[key] = value;
49
+ }
50
+ }
51
+ }
52
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/onboard/analyzer.ts"],"names":[],"mappings":"AAwBA,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAe,EACf,SAAqB;IAErB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QACxB,IAAI,CAAC;YACH,OAAO;gBACL,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC9B,KAAK,EAAE,IAAqB;aAC7B,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO;gBACL,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;aAClD,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9C,SAAS;QACX,CAAC;QACD,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI;YAAE,SAAS;QAC/B,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,gBAAgB,GAAG,MAAM,CAAC;IACxD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,SAAS,CAChB,MAAsB,EACtB,MAA6B;IAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAG7C,EAAE,CAAC;QACJ,IAAI,KAAK,KAAK,SAAS;YAAE,SAAS;QAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAI,MAAM,CAAC,GAAG,CAA2B,IAAI,EAAE,CAAC;YAC7D,MAAkC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC;QACrE,CAAC;aAAM,IACL,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EACrB,CAAC;YACD,MAAM,QAAQ,GACX,MAAM,CAAC,GAAG,CAAyC,IAAI,EAAE,CAAC;YAC5D,MAAkC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;QACvE,CAAC;aAAM,CAAC;YACL,MAAkC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Analyzer } from "../analyzer.js";
2
+ export declare const ciAnalyzer: Analyzer;
3
+ //# sourceMappingURL=ci.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/onboard/analyzers/ci.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAc/C,eAAO,MAAM,UAAU,EAAE,QA8CxB,CAAC"}
@@ -0,0 +1,112 @@
1
+ // packages/cli/src/onboard/analyzers/ci.ts
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { parse as parseYaml } from "yaml";
5
+ const DEPLOY_VERBS = [
6
+ { re: /\bhelm\s+(upgrade|install)\b/, target: "helm" },
7
+ { re: /\bgh\s+release\s+create\b/, target: "gh-release" },
8
+ { re: /\bgcloud\s+run\s+deploy\b/, target: "gcloud-run" },
9
+ { re: /\bgcloud\s+builds\s+submit\b/, target: "gcloud-run" },
10
+ { re: /\baws\s+ecs\s+update-service\b/, target: "ecs" },
11
+ { re: /\bvercel\s+deploy\b/, target: "vercel" },
12
+ { re: /\bflyctl\s+deploy\b/, target: "fly" },
13
+ { re: /\bkubectl\s+apply\b/, target: "kubernetes" },
14
+ ];
15
+ export const ciAnalyzer = {
16
+ name: "ci",
17
+ async detect(rootDir) {
18
+ const wfDir = join(rootDir, ".github", "workflows");
19
+ let entries;
20
+ try {
21
+ entries = (await readdir(wfDir)).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ if (entries.length === 0)
27
+ return null;
28
+ const workflows = [];
29
+ let deployStory = null;
30
+ for (const name of entries) {
31
+ const path = join(wfDir, name);
32
+ const raw = await readFile(path, "utf8");
33
+ let parsed;
34
+ try {
35
+ parsed = parseYaml(raw);
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ if (!parsed || typeof parsed !== "object")
41
+ continue;
42
+ const p = parsed;
43
+ const triggers = normalizeTriggers(p.on);
44
+ const jobs = Object.keys(p.jobs ?? {});
45
+ const matrixDimensions = collectMatrixDims(p);
46
+ workflows.push({
47
+ name: typeof p.name === "string" ? p.name : name,
48
+ path: `.github/workflows/${name}`,
49
+ triggers,
50
+ jobs,
51
+ matrixDimensions,
52
+ });
53
+ if (!deployStory) {
54
+ deployStory = findDeployCommand(p, `.github/workflows/${name}`);
55
+ }
56
+ }
57
+ return { ci: { workflows, deployStory } };
58
+ },
59
+ };
60
+ function normalizeTriggers(on) {
61
+ if (typeof on === "string")
62
+ return [on];
63
+ if (Array.isArray(on))
64
+ return on.filter((x) => typeof x === "string");
65
+ if (on && typeof on === "object")
66
+ return Object.keys(on);
67
+ return [];
68
+ }
69
+ function collectMatrixDims(parsed) {
70
+ const dims = new Set();
71
+ const jobs = parsed.jobs ?? {};
72
+ for (const job of Object.values(jobs)) {
73
+ if (!job || typeof job !== "object")
74
+ continue;
75
+ const strategy = job.strategy;
76
+ if (!strategy || typeof strategy !== "object")
77
+ continue;
78
+ const matrix = strategy.matrix;
79
+ if (!matrix || typeof matrix !== "object")
80
+ continue;
81
+ for (const k of Object.keys(matrix)) {
82
+ if (k !== "include" && k !== "exclude")
83
+ dims.add(k);
84
+ }
85
+ }
86
+ return [...dims];
87
+ }
88
+ function findDeployCommand(parsed, workflowPath) {
89
+ const jobs = parsed.jobs ?? {};
90
+ for (const job of Object.values(jobs)) {
91
+ if (!job || typeof job !== "object")
92
+ continue;
93
+ const steps = job.steps;
94
+ if (!Array.isArray(steps))
95
+ continue;
96
+ for (const step of steps) {
97
+ if (!step || typeof step !== "object")
98
+ continue;
99
+ const run = step.run;
100
+ if (typeof run !== "string")
101
+ continue;
102
+ for (const line of run.split(/\n/)) {
103
+ for (const { re, target } of DEPLOY_VERBS) {
104
+ if (re.test(line))
105
+ return { workflowPath, command: line.trim(), target };
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ //# sourceMappingURL=ci.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.js","sourceRoot":"","sources":["../../../src/onboard/analyzers/ci.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAI1C,MAAM,YAAY,GAAyD;IACzE,EAAE,EAAE,EAAE,8BAA8B,EAAE,MAAM,EAAE,MAAM,EAAE;IACtD,EAAE,EAAE,EAAE,2BAA2B,EAAE,MAAM,EAAE,YAAY,EAAE;IACzD,EAAE,EAAE,EAAE,2BAA2B,EAAE,MAAM,EAAE,YAAY,EAAE;IACzD,EAAE,EAAE,EAAE,8BAA8B,EAAE,MAAM,EAAE,YAAY,EAAE;IAC5D,EAAE,EAAE,EAAE,gCAAgC,EAAE,MAAM,EAAE,KAAK,EAAE;IACvD,EAAE,EAAE,EAAE,qBAAqB,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC/C,EAAE,EAAE,EAAE,qBAAqB,EAAE,MAAM,EAAE,KAAK,EAAE;IAC5C,EAAE,EAAE,EAAE,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE;CACpD,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAa;IAClC,IAAI,EAAE,IAAI;IACV,KAAK,CAAC,MAAM,CAAC,OAAO;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QACpD,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CACrC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACjD,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,SAAS,GAAe,EAAE,CAAC;QACjC,IAAI,WAAW,GAAuB,IAAI,CAAC;QAE3C,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC/B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACzC,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;gBAAE,SAAS;YACpD,MAAM,CAAC,GAAG,MAAiC,CAAC;YAC5C,MAAM,QAAQ,GAAG,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CACrB,CAAC,CAAC,IAA4C,IAAI,EAAE,CACtD,CAAC;YACF,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;YAC9C,SAAS,CAAC,IAAI,CAAC;gBACb,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;gBAChD,IAAI,EAAE,qBAAqB,IAAI,EAAE;gBACjC,QAAQ;gBACR,IAAI;gBACJ,gBAAgB;aACjB,CAAC,CAAC;YACH,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,WAAW,GAAG,iBAAiB,CAAC,CAAC,EAAE,qBAAqB,IAAI,EAAE,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,CAAC;IAC5C,CAAC;CACF,CAAC;AAEF,SAAS,iBAAiB,CAAC,EAAW;IACpC,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IAC9D,IAAI,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ;QAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,EAA6B,CAAC,CAAC;IACpD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,iBAAiB,CAAC,MAA+B;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAI,MAAM,CAAC,IAA4C,IAAI,EAAE,CAAC;IACxE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,QAAQ,GAAI,GAA+B,CAAC,QAAQ,CAAC;QAC3D,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACxD,MAAM,MAAM,GAAI,QAAoC,CAAC,MAAM,CAAC;QAC5D,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS;QACpD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAiC,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CACxB,MAA+B,EAC/B,YAAoB;IAEpB,MAAM,IAAI,GAAI,MAAM,CAAC,IAA4C,IAAI,EAAE,CAAC;IACxE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,KAAK,GAAI,GAA+B,CAAC,KAAK,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,SAAS;QACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,SAAS;YAChD,MAAM,GAAG,GAAI,IAAgC,CAAC,GAAG,CAAC;YAClD,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,SAAS;YACtC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,KAAK,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;oBAC1C,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;wBACf,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC;gBAC1D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Analyzer } from "../analyzer.js";
2
+ export declare const docsAnalyzer: Analyzer;
3
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../src/onboard/analyzers/docs.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA8F/C,eAAO,MAAM,YAAY,EAAE,QAsD1B,CAAC"}