@dadado/agent-kit-cli 4.4.5 → 4.5.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 (2) hide show
  1. package/dist/index.js +1828 -1002
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { defineCommand as defineCommand11, runMain } from "citty";
4
+ import { defineCommand as defineCommand12, runMain } from "citty";
5
5
 
6
6
  // src/commands/add.ts
7
7
  import { defineCommand } from "citty";
@@ -18,8 +18,11 @@ var DEFAULT_PROTECTED_PATHS = [
18
18
  ".cursor/HANDOFF.md",
19
19
  ".cursor/plans/**",
20
20
  ".cursor/memory/**",
21
- ".cursor/context/**"
21
+ ".cursor/context/config.json",
22
+ ".cursor/context/current/**",
23
+ ".cursor/context/backups/**"
22
24
  ];
25
+ var LEGACY_CONTEXT_PROTECTED_GLOB = ".cursor/context/**";
23
26
  var DOMAIN_PACK_IDS = [
24
27
  "cybersec",
25
28
  "devops",
@@ -63,12 +66,12 @@ async function listDirectory(rootDir) {
63
66
  import path2 from "path";
64
67
  function resolveContained(root, rel) {
65
68
  const rootAbs = path2.resolve(root);
66
- const candidate = path2.resolve(rootAbs, rel);
67
- const relToRoot = path2.relative(rootAbs, candidate);
69
+ const candidate2 = path2.resolve(rootAbs, rel);
70
+ const relToRoot = path2.relative(rootAbs, candidate2);
68
71
  if (relToRoot.startsWith("..") || path2.isAbsolute(relToRoot)) {
69
72
  throw new Error(`Path escapes registry/project root: ${rel}`);
70
73
  }
71
- return candidate;
74
+ return candidate2;
72
75
  }
73
76
  function toPosixRel(root, absPath) {
74
77
  return path2.relative(root, absPath).split(path2.sep).join("/");
@@ -116,11 +119,32 @@ function matchesAnyGlob(relPath, globs) {
116
119
  }
117
120
 
118
121
  // src/lifecycle/protected.ts
122
+ var CONTEXT_SESSION_GLOBS = [
123
+ ".cursor/context/config.json",
124
+ ".cursor/context/current/**",
125
+ ".cursor/context/backups/**"
126
+ ];
127
+ function normalizeProtectedGlobs(globs) {
128
+ const out = [];
129
+ let sawLegacyContext = false;
130
+ for (const raw of globs) {
131
+ const g = normalizeRelPath(raw);
132
+ if (g === LEGACY_CONTEXT_PROTECTED_GLOB) {
133
+ sawLegacyContext = true;
134
+ continue;
135
+ }
136
+ out.push(g);
137
+ }
138
+ if (sawLegacyContext) {
139
+ out.push(...CONTEXT_SESSION_GLOBS);
140
+ }
141
+ return [...new Set(out)];
142
+ }
119
143
  function resolveProtectedGlobs(manifest) {
120
144
  const fromManifest = manifest?.protected ?? [];
121
145
  const overrides = (manifest?.overrides ?? []).map((o) => o.path);
122
146
  const merged = [...DEFAULT_PROTECTED_PATHS, ...fromManifest, ...overrides];
123
- return [...new Set(merged.map(normalizeRelPath))];
147
+ return normalizeProtectedGlobs(merged);
124
148
  }
125
149
  function isProtectedPath(relPath, globs) {
126
150
  return matchesAnyGlob(relPath, globs);
@@ -128,10 +152,19 @@ function isProtectedPath(relPath, globs) {
128
152
 
129
153
  // src/lifecycle/apply.ts
130
154
  function emptyStats() {
131
- return { written: [], skippedProtected: [], missing: [], unchanged: [] };
155
+ return {
156
+ written: [],
157
+ removed: [],
158
+ collisions: [],
159
+ skippedProtected: [],
160
+ missing: [],
161
+ unchanged: []
162
+ };
132
163
  }
133
164
  function mergeStats(into, from) {
134
165
  into.written.push(...from.written);
166
+ into.removed.push(...from.removed);
167
+ into.collisions.push(...from.collisions);
135
168
  into.skippedProtected.push(...from.skippedProtected);
136
169
  into.missing.push(...from.missing);
137
170
  into.unchanged.push(...from.unchanged);
@@ -192,11 +225,12 @@ function buildManifest(input) {
192
225
  const manifest = {
193
226
  schemaVersion: 1,
194
227
  version: input.version,
195
- protected: input.protected ?? [...DEFAULT_PROTECTED_PATHS]
228
+ protected: normalizeProtectedGlobs(input.protected ?? [...DEFAULT_PROTECTED_PATHS])
196
229
  };
197
230
  if (input.profile) manifest.profile = input.profile;
198
231
  if (input.packs?.length) manifest.packs = [...new Set(input.packs)].sort();
199
232
  if (input.skills?.length) manifest.skills = [...new Set(input.skills)].sort();
233
+ if (input.personalization) manifest.personalization = input.personalization;
200
234
  if (input.registryUrl || input.registryRef) {
201
235
  manifest.registry = {};
202
236
  if (input.registryUrl) manifest.registry.url = input.registryUrl;
@@ -234,6 +268,14 @@ function logApplyStats(stats) {
234
268
  if (stats.unchanged.length > 0) {
235
269
  logger.info(`Unchanged: ${stats.unchanged.length}`);
236
270
  }
271
+ if (stats.removed.length > 0) {
272
+ logger.info(`Removed managed legacy files: ${stats.removed.length}`);
273
+ for (const p of stats.removed) logger.info(` - ${p}`);
274
+ }
275
+ if (stats.collisions.length > 0) {
276
+ logger.warn("Slash collision preserved because the legacy command is customized:");
277
+ for (const p of stats.collisions) logger.info(` ! ${p}`);
278
+ }
237
279
  if (stats.skippedProtected.length > 0) {
238
280
  logger.warn(`Skipped protected (L3): ${stats.skippedProtected.length}`);
239
281
  for (const p of stats.skippedProtected) logger.info(` ~ ${p}`);
@@ -370,7 +412,21 @@ var REGISTRY_CLI_ARGS = {
370
412
  };
371
413
 
372
414
  // src/lifecycle/version.ts
373
- var KIT_VERSION = "3.0.0";
415
+ import { createRequire } from "module";
416
+ var require2 = createRequire(import.meta.url);
417
+ function loadPackageVersion() {
418
+ for (const packagePath of ["../package.json", "../../package.json"]) {
419
+ try {
420
+ const metadata = require2(packagePath);
421
+ if (typeof metadata.version === "string" && metadata.version.length > 0) {
422
+ return metadata.version;
423
+ }
424
+ } catch {
425
+ }
426
+ }
427
+ throw new Error("Unable to resolve the Agent Kit CLI package version");
428
+ }
429
+ var KIT_VERSION = loadPackageVersion();
374
430
 
375
431
  // src/manifest/index.ts
376
432
  import path5 from "path";
@@ -494,6 +550,21 @@ function parseAgentKitManifest(raw) {
494
550
  }
495
551
  }
496
552
  }
553
+ let personalization;
554
+ if (rest.personalization !== void 0) {
555
+ if (!isPlainObject(rest.personalization)) {
556
+ issues.push("personalization must be an object");
557
+ } else if (typeof rest.personalization.contractVersion !== "number" || typeof rest.personalization.generatorVersion !== "string" || rest.personalization.origin !== "repository-profile" || typeof rest.personalization.resultPath !== "string") {
558
+ issues.push("personalization must contain a valid origin and version contract");
559
+ } else {
560
+ personalization = {
561
+ contractVersion: rest.personalization.contractVersion,
562
+ generatorVersion: rest.personalization.generatorVersion,
563
+ origin: "repository-profile",
564
+ resultPath: rest.personalization.resultPath
565
+ };
566
+ }
567
+ }
497
568
  if (rest.installedAt !== void 0 && typeof rest.installedAt !== "string") {
498
569
  issues.push("installedAt must be a string");
499
570
  }
@@ -506,6 +577,7 @@ function parseAgentKitManifest(raw) {
506
577
  "protected",
507
578
  "overrides",
508
579
  "registry",
580
+ "personalization",
509
581
  "installedAt"
510
582
  ]);
511
583
  for (const key of Object.keys(rest)) {
@@ -526,6 +598,7 @@ function parseAgentKitManifest(raw) {
526
598
  if (protectedPaths) manifest.protected = protectedPaths;
527
599
  if (overrides) manifest.overrides = overrides;
528
600
  if (registry) manifest.registry = registry;
601
+ if (personalization) manifest.personalization = personalization;
529
602
  if (typeof rest.installedAt === "string") manifest.installedAt = rest.installedAt;
530
603
  return manifest;
531
604
  }
@@ -888,6 +961,10 @@ var L0_ARTIFACTS = [
888
961
  target: ".cursor/rules/docs-professional-standard.mdc"
889
962
  },
890
963
  { source: ".cursor/rules/memory-loop.mdc", target: ".cursor/rules/memory-loop.mdc" },
964
+ {
965
+ source: ".cursor/rules/hitl-ask-questions.mdc",
966
+ target: ".cursor/rules/hitl-ask-questions.mdc"
967
+ },
891
968
  {
892
969
  source: "registry/rules/git-secrets-safety.mdc",
893
970
  target: ".cursor/rules/git-secrets-safety.mdc"
@@ -898,8 +975,8 @@ var L0_ARTIFACTS = [
898
975
  target: ".cursor/commands/start-project.md"
899
976
  },
900
977
  {
901
- source: ".cursor/commands/onboard.md",
902
- target: ".cursor/commands/onboard.md"
978
+ source: ".cursor/commands/agent-kit-onboard.md",
979
+ target: ".cursor/commands/agent-kit-onboard.md"
903
980
  },
904
981
  {
905
982
  source: ".cursor/commands/continue-plan.md",
@@ -925,6 +1002,52 @@ var L0_ARTIFACTS = [
925
1002
  target: ".cursor/commands/git-staging.md"
926
1003
  },
927
1004
  { source: ".cursor/commands/git-prod.md", target: ".cursor/commands/git-prod.md" },
1005
+ {
1006
+ source: ".cursor/commands/plan-external-review.md",
1007
+ target: ".cursor/commands/plan-external-review.md"
1008
+ },
1009
+ {
1010
+ source: ".cursor/commands/plan-review-triage.md",
1011
+ target: ".cursor/commands/plan-review-triage.md"
1012
+ },
1013
+ // Context (templates + example config; private config.json is not L0)
1014
+ {
1015
+ source: ".cursor/context/templates/plan-external-review-prompt.md",
1016
+ target: ".cursor/context/templates/plan-external-review-prompt.md"
1017
+ },
1018
+ {
1019
+ source: ".cursor/context/templates/plan.md",
1020
+ target: ".cursor/context/templates/plan.md"
1021
+ },
1022
+ {
1023
+ source: ".cursor/context/templates/context-pack.md",
1024
+ target: ".cursor/context/templates/context-pack.md"
1025
+ },
1026
+ {
1027
+ source: ".cursor/context/templates/task-brief.md",
1028
+ target: ".cursor/context/templates/task-brief.md"
1029
+ },
1030
+ {
1031
+ source: ".cursor/context/templates/handoff.md",
1032
+ target: ".cursor/context/templates/handoff.md"
1033
+ },
1034
+ {
1035
+ source: ".cursor/context/templates/adr.md",
1036
+ target: ".cursor/context/templates/adr.md"
1037
+ },
1038
+ {
1039
+ source: ".cursor/context/templates/plan-monitor.md",
1040
+ target: ".cursor/context/templates/plan-monitor.md"
1041
+ },
1042
+ {
1043
+ source: ".cursor/context/config.example.json",
1044
+ target: ".cursor/context/config.example.json"
1045
+ },
1046
+ // Scripts (canonical launcher; consumers never receive repo-root scripts/)
1047
+ {
1048
+ source: ".cursor/scripts/plan-external-review.sh",
1049
+ target: ".cursor/scripts/plan-external-review.sh"
1050
+ },
928
1051
  // Secrets gate (structural)
929
1052
  {
930
1053
  source: ".cursor/hooks/pre-commit/check-secrets.sh",
@@ -1366,40 +1489,592 @@ var diffCommand = defineCommand3({
1366
1489
  }
1367
1490
  });
1368
1491
 
1369
- // src/commands/handoff.ts
1370
- import { spawn } from "child_process";
1371
- import { readFile as readFile6, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
1372
- import path10 from "path";
1492
+ // src/commands/doctor.ts
1493
+ import path19 from "path";
1373
1494
  import { defineCommand as defineCommand4 } from "citty";
1374
1495
 
1496
+ // src/scanner/readiness.ts
1497
+ import { createHash as createHash2 } from "crypto";
1498
+ function action(id, status, recommendation, owner) {
1499
+ return { id, status, recommendation, owner };
1500
+ }
1501
+ function check(id, title, status, essential, evidence, actions = []) {
1502
+ return { id, title, status, essential, evidence, actions };
1503
+ }
1504
+ function pillar(pillarName, checks) {
1505
+ return { pillar: pillarName, checks };
1506
+ }
1507
+ function repositoryFingerprint(scan) {
1508
+ const fingerprintInput = {
1509
+ purpose: scan.purpose,
1510
+ stack: {
1511
+ language: scan.stack.language,
1512
+ framework: scan.stack.framework,
1513
+ packageManager: scan.stack.packageManager,
1514
+ workspaces: scan.stack.workspaces
1515
+ },
1516
+ git: {
1517
+ mode: scan.git.mode,
1518
+ remoteUrl: scan.git.remoteUrl,
1519
+ currentBranch: scan.git.currentBranch,
1520
+ defaultBranch: scan.git.defaultBranch
1521
+ },
1522
+ context: scan.context.sources,
1523
+ agentKitVersion: scan.agentKit.version
1524
+ };
1525
+ return createHash2("sha256").update(JSON.stringify(fingerprintInput)).digest("hex");
1526
+ }
1527
+ function buildPillars(scan) {
1528
+ const agentKitStatus = scan.agentKit.installed ? "ready" : "auto_fix";
1529
+ const purposeStatus = scan.purpose.value === "unknown" ? "needs_choice" : "ready";
1530
+ const gitStatus = scan.git.mode === "none" ? "needs_choice" : "ready";
1531
+ const secretsStatus = scan.safety.trackedSensitiveFiles.length > 0 ? "blocked" : scan.safety.missingSecretPatterns.length > 0 ? "auto_fix" : "ready";
1532
+ const contextStatus = scan.context.sources.length > 0 ? "ready" : "needs_choice";
1533
+ const providerStatus = scan.git.mode === "none" ? "needs_choice" : scan.git.mode === "local-only" ? "ready" : scan.git.providerKind === "unknown" || scan.git.providerKind === "custom" && scan.git.providerConfidence === "low" ? "needs_choice" : "ready";
1534
+ return [
1535
+ pillar("workspace", [
1536
+ check(
1537
+ "workspace.agent-kit",
1538
+ "Agent Kit installation",
1539
+ agentKitStatus,
1540
+ true,
1541
+ scan.agentKit.manifestPath ? [{ source: "file", value: scan.agentKit.manifestPath }] : [],
1542
+ agentKitStatus === "ready" ? [] : [action("install-agent-kit", "auto_fix", "Install the managed L0 inventory", "system")]
1543
+ )
1544
+ ]),
1545
+ pillar("purpose-context", [
1546
+ check(
1547
+ "purpose.classification",
1548
+ "Repository purpose",
1549
+ purposeStatus,
1550
+ true,
1551
+ scan.purpose.evidence,
1552
+ purposeStatus === "ready" ? [] : [
1553
+ action(
1554
+ "confirm-repository-purpose",
1555
+ "needs_choice",
1556
+ "Confirm the stable repository purpose",
1557
+ "user"
1558
+ )
1559
+ ]
1560
+ ),
1561
+ check(
1562
+ "context.sources",
1563
+ "Project context sources",
1564
+ contextStatus,
1565
+ true,
1566
+ scan.context.sources,
1567
+ contextStatus === "ready" ? [] : [
1568
+ action(
1569
+ "identify-context-source",
1570
+ "needs_choice",
1571
+ "Identify the repository source of truth",
1572
+ "user"
1573
+ )
1574
+ ]
1575
+ )
1576
+ ]),
1577
+ pillar("source-control", [
1578
+ check(
1579
+ "git.repository",
1580
+ "Git repository",
1581
+ gitStatus,
1582
+ true,
1583
+ scan.git.currentBranch ? [{ source: "git", value: `branch:${scan.git.currentBranch}` }] : [],
1584
+ gitStatus === "ready" ? [] : [
1585
+ action(
1586
+ "choose-source-control",
1587
+ "needs_choice",
1588
+ "Choose whether to initialize Git",
1589
+ "user"
1590
+ )
1591
+ ]
1592
+ ),
1593
+ check(
1594
+ "git.staging-branch",
1595
+ "Staging branch",
1596
+ scan.git.hasLocalStaging || scan.git.hasRemoteStaging ? "ready" : "needs_choice",
1597
+ false,
1598
+ [
1599
+ ...scan.git.hasLocalStaging ? [{ source: "git", value: "local branch:staging" }] : [],
1600
+ ...scan.git.hasRemoteStaging ? [{ source: "git", value: "remote branch:staging" }] : []
1601
+ ],
1602
+ scan.git.hasLocalStaging || scan.git.hasRemoteStaging ? [] : [
1603
+ action(
1604
+ "choose-branch-strategy",
1605
+ "needs_choice",
1606
+ "Confirm the repository promotion strategy",
1607
+ "user"
1608
+ )
1609
+ ]
1610
+ )
1611
+ ]),
1612
+ pillar("safety", [
1613
+ check(
1614
+ "safety.secrets",
1615
+ "Secrets hygiene",
1616
+ secretsStatus,
1617
+ true,
1618
+ [
1619
+ ...scan.safety.ignoredSecretPatterns.map((value) => ({
1620
+ source: "file",
1621
+ value: `.gitignore:${value}`
1622
+ })),
1623
+ ...scan.safety.trackedSensitiveFiles.map((value) => ({
1624
+ source: "git",
1625
+ value: `tracked:${value}`
1626
+ }))
1627
+ ],
1628
+ secretsStatus === "ready" ? [] : secretsStatus === "blocked" ? [
1629
+ action(
1630
+ "remove-tracked-secrets",
1631
+ "blocked",
1632
+ "Remove tracked sensitive files and rotate exposed values",
1633
+ "user"
1634
+ )
1635
+ ] : [
1636
+ action(
1637
+ "merge-secret-ignores",
1638
+ "auto_fix",
1639
+ "Merge required secret patterns into .gitignore",
1640
+ "system"
1641
+ )
1642
+ ]
1643
+ )
1644
+ ]),
1645
+ pillar("stack-tooling", [
1646
+ check(
1647
+ "stack.detected",
1648
+ "Stack and package manager",
1649
+ scan.stack.language !== "unknown" || scan.purpose.value !== "unknown" ? "ready" : "needs_choice",
1650
+ true,
1651
+ scan.stack.packageManagerEvidence ?? scan.purpose.evidence
1652
+ )
1653
+ ]),
1654
+ pillar("quality-ci", [
1655
+ check(
1656
+ "quality.validation",
1657
+ "Tests and validation",
1658
+ scan.quality.hasTests || scan.quality.validationCommands.length > 0 ? "ready" : "manual",
1659
+ false,
1660
+ scan.infra.ciFiles.map((value) => ({ source: "file", value })),
1661
+ scan.quality.hasTests || scan.quality.validationCommands.length > 0 ? [] : [
1662
+ action(
1663
+ "document-validation",
1664
+ "manual",
1665
+ "Document a repeatable repository validation command",
1666
+ "user"
1667
+ )
1668
+ ]
1669
+ )
1670
+ ]),
1671
+ pillar("deploy-infrastructure", [
1672
+ check(
1673
+ "infrastructure.detected",
1674
+ "Deployment and infrastructure evidence",
1675
+ "ready",
1676
+ false,
1677
+ [...scan.infra.infrastructureFiles, ...scan.infra.deploymentFiles].map((value) => ({
1678
+ source: "file",
1679
+ value
1680
+ }))
1681
+ )
1682
+ ]),
1683
+ pillar("collaboration", [
1684
+ check(
1685
+ "collaboration.provider",
1686
+ "Repository provider",
1687
+ providerStatus,
1688
+ false,
1689
+ scan.git.providerEvidence,
1690
+ providerStatus === "ready" ? [] : [
1691
+ action(
1692
+ "confirm-provider",
1693
+ "needs_choice",
1694
+ "Confirm the remote provider or local-only model",
1695
+ "user"
1696
+ )
1697
+ ]
1698
+ )
1699
+ ]),
1700
+ pillar("agent-kit-personalization", [
1701
+ check(
1702
+ "agent-kit.context",
1703
+ "Agent guidance",
1704
+ scan.context.hasAgentGuidance ? "ready" : "auto_fix",
1705
+ false,
1706
+ scan.context.sources.filter((item) => item.value.includes("guidance")),
1707
+ scan.context.hasAgentGuidance ? [] : [
1708
+ action(
1709
+ "prepare-agent-context",
1710
+ "auto_fix",
1711
+ "Prepare Agent Kit-owned context from detected evidence",
1712
+ "system"
1713
+ )
1714
+ ]
1715
+ )
1716
+ ])
1717
+ ];
1718
+ }
1719
+ function createReadinessReport(scan, options) {
1720
+ const pillars = buildPillars(scan);
1721
+ const checks = pillars.flatMap((item) => item.checks);
1722
+ const statuses = ["ready", "auto_fix", "needs_choice", "manual", "blocked"];
1723
+ const summary = Object.fromEntries(
1724
+ statuses.map((status) => [status, checks.filter((item) => item.status === status).length])
1725
+ );
1726
+ const pendingActions = checks.flatMap((item) => item.actions).filter((item) => item.status !== "ready");
1727
+ const { rootDir: _rootDir, ...portableScan } = scan;
1728
+ return {
1729
+ schemaVersion: 1,
1730
+ generatorVersion: options.generatorVersion,
1731
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1732
+ repositoryFingerprint: repositoryFingerprint(scan),
1733
+ summary,
1734
+ scan: portableScan,
1735
+ pillars,
1736
+ appliedSafeFixes: [],
1737
+ pendingActions,
1738
+ deferredChecks: []
1739
+ };
1740
+ }
1741
+
1742
+ // src/scanner/safe-fixes.ts
1743
+ import { readFile as readFile8, writeFile as writeFile3 } from "fs/promises";
1744
+ import path17 from "path";
1745
+
1746
+ // src/scanner/detect-repository.ts
1747
+ import { readFile as readFile6 } from "fs/promises";
1748
+ import path10 from "path";
1749
+ var CONTEXT_PATHS = [
1750
+ ["README.md", "README"],
1751
+ ["README", "README"],
1752
+ ["AGENTS.md", "agent guidance"],
1753
+ ["CLAUDE.md", "agent guidance"],
1754
+ [".cursor/rules", "Cursor rules"],
1755
+ ["docs/architecture.md", "architecture"],
1756
+ ["docs/adr", "architecture decisions"],
1757
+ ["docs/runbooks", "runbooks"],
1758
+ ["runbooks", "runbooks"],
1759
+ ["prompts", "prompts"],
1760
+ ["schemas", "schemas"],
1761
+ ["sql", "SQL"],
1762
+ ["knowledge", "knowledge base"]
1763
+ ];
1764
+ async function existingEvidence(rootDir, candidates) {
1765
+ const evidence = await Promise.all(
1766
+ candidates.map(
1767
+ async ([relativePath, label]) => await fileExists(path10.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
1768
+ )
1769
+ );
1770
+ return evidence.flatMap((item) => item ? [item] : []);
1771
+ }
1772
+ async function detectContext(rootDir) {
1773
+ const sources = await existingEvidence(rootDir, CONTEXT_PATHS);
1774
+ const paths = sources.map((item) => item.value.split(":")[0]);
1775
+ return {
1776
+ sources,
1777
+ hasReadme: paths.some((item) => item === "README" || item === "README.md"),
1778
+ hasArchitecture: sources.some((item) => item.value.includes("architecture")),
1779
+ hasRunbooks: sources.some((item) => item.value.includes("runbooks")),
1780
+ hasAgentGuidance: sources.some(
1781
+ (item) => item.value.includes("agent guidance") || item.value.includes("Cursor rules")
1782
+ )
1783
+ };
1784
+ }
1785
+ async function detectPurpose(rootDir, stack) {
1786
+ const entries = await listDirectory(rootDir);
1787
+ const lowerEntries = entries.map((entry) => entry.toLowerCase());
1788
+ const packageJson = await readJson(path10.join(rootDir, "package.json"));
1789
+ const categories = [];
1790
+ const evidence = [];
1791
+ const add = (category, value2) => {
1792
+ if (!categories.includes(category)) categories.push(category);
1793
+ evidence.push({ source: "file", value: value2 });
1794
+ };
1795
+ if (stack.workspaces) add("monorepo", "workspace configuration");
1796
+ else if (stack.language === "node" && packageJson?.private !== true && (packageJson?.exports !== void 0 || packageJson?.main || packageJson?.types)) {
1797
+ add("library", "package export configuration");
1798
+ } else if (stack.hasProjectFiles) add("application", "application package marker");
1799
+ if (lowerEntries.some((entry) => ["docs", "documentation", "mkdocs.yml"].includes(entry))) {
1800
+ add("documentation", "documentation structure");
1801
+ }
1802
+ if (lowerEntries.some(
1803
+ (entry) => ["knowledge", "content", "wiki", "playbooks", "handbook"].includes(entry)
1804
+ )) {
1805
+ add("knowledge", "knowledge structure");
1806
+ }
1807
+ if (lowerEntries.some(
1808
+ (entry) => ["runbooks", "infra", "terraform", "ansible", "k8s", "kubernetes"].includes(entry)
1809
+ )) {
1810
+ add("operations", "operations structure");
1811
+ }
1812
+ if (lowerEntries.some(
1813
+ (entry) => ["n8n", "workflows", "automations", "prompts", "sql", "schemas"].includes(entry)
1814
+ ) || lowerEntries.some((entry) => entry.endsWith(".sql"))) {
1815
+ add("automation", "automation or data artifact");
1816
+ }
1817
+ const meaningfulCategories = categories.filter((category) => category !== "unknown");
1818
+ const value = meaningfulCategories.length === 0 ? "unknown" : meaningfulCategories.length === 1 ? meaningfulCategories[0] ?? "unknown" : "mixed";
1819
+ return {
1820
+ value,
1821
+ categories: meaningfulCategories.length > 0 ? meaningfulCategories : ["unknown"],
1822
+ confidence: evidence.length > 0 ? "high" : "low",
1823
+ evidence
1824
+ };
1825
+ }
1826
+ async function detectAgentKit(rootDir) {
1827
+ const manifestRelativePath = ".cursor/agent-kit.json";
1828
+ const manifestPath = path10.join(rootDir, manifestRelativePath);
1829
+ const installed = await fileExists(manifestPath);
1830
+ const manifest = installed ? await readJson(manifestPath) : null;
1831
+ return {
1832
+ installed,
1833
+ manifestPath: installed ? manifestRelativePath : void 0,
1834
+ version: manifest?.version,
1835
+ hasPlans: await fileExists(path10.join(rootDir, ".cursor/plans")),
1836
+ hasHandoff: await fileExists(path10.join(rootDir, ".cursor/HANDOFF.md")),
1837
+ hasMemory: await fileExists(path10.join(rootDir, ".cursor/memory"))
1838
+ };
1839
+ }
1840
+ var REQUIRED_SECRET_PATTERNS = [
1841
+ ".env",
1842
+ ".env.*",
1843
+ "*.key",
1844
+ "*.pem",
1845
+ "*.p12",
1846
+ "*.pfx",
1847
+ "*credentials*.json",
1848
+ "*service-account*.json"
1849
+ ];
1850
+ async function detectSafety(rootDir, trackedFiles) {
1851
+ const gitignorePath = path10.join(rootDir, ".gitignore");
1852
+ const hasGitignore = await fileExists(gitignorePath);
1853
+ const gitignore = hasGitignore ? await readFile6(gitignorePath, "utf8") : "";
1854
+ const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1855
+ const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
1856
+ (pattern) => lines.includes(pattern)
1857
+ );
1858
+ const trackedSensitiveFiles = trackedFiles.filter(
1859
+ (file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
1860
+ );
1861
+ const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
1862
+ const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path10.join(rootDir, item))))).some(Boolean);
1863
+ const guardCandidates = [
1864
+ ".husky/pre-commit",
1865
+ ".husky/pre-push",
1866
+ "git-hooks/pre-commit",
1867
+ "git-hooks/pre-push"
1868
+ ];
1869
+ const guardContents = await Promise.all(
1870
+ guardCandidates.map(
1871
+ async (item) => await fileExists(path10.join(rootDir, item)) ? readFile6(path10.join(rootDir, item), "utf8") : ""
1872
+ )
1873
+ );
1874
+ return {
1875
+ hasGitignore,
1876
+ ignoredSecretPatterns,
1877
+ missingSecretPatterns: REQUIRED_SECRET_PATTERNS.filter(
1878
+ (pattern) => !ignoredSecretPatterns.includes(pattern)
1879
+ ),
1880
+ trackedSensitiveFiles,
1881
+ hasHooks,
1882
+ hasMainBranchGuard: guardContents.some(
1883
+ (content) => content.includes("main") || content.includes("master")
1884
+ )
1885
+ };
1886
+ }
1887
+
1888
+ // src/scanner/scan.ts
1889
+ import path16 from "path";
1890
+
1891
+ // src/scanner/detect-git.ts
1892
+ import { execFile as execFile2 } from "child_process";
1893
+ import path11 from "path";
1894
+ import { promisify as promisify2 } from "util";
1895
+ var exec = promisify2(execFile2);
1896
+ function remoteHostname(remoteUrl) {
1897
+ const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):/);
1898
+ if (scpMatch?.[1]) return scpMatch[1].toLowerCase();
1899
+ try {
1900
+ return new URL(remoteUrl).hostname.toLowerCase();
1901
+ } catch {
1902
+ return void 0;
1903
+ }
1904
+ }
1905
+ function sanitizeRemoteUrl(remoteUrl) {
1906
+ try {
1907
+ const parsed = new URL(remoteUrl);
1908
+ parsed.username = "";
1909
+ parsed.password = "";
1910
+ return parsed.toString();
1911
+ } catch {
1912
+ return remoteUrl;
1913
+ }
1914
+ }
1915
+ async function detectProvider(rootDir, remoteUrl) {
1916
+ const configuration = await readJson(
1917
+ path11.join(rootDir, ".cursor", "agent-kit.config.json")
1918
+ );
1919
+ const configuredProvider = configuration?.git?.provider;
1920
+ if (configuredProvider) {
1921
+ const configuredKind = configuration.git?.providerKind ?? (configuredProvider === "github" ? "github" : configuredProvider === "gitlab" ? remoteUrl && remoteHostname(remoteUrl) === "gitlab.com" ? "gitlab-saas" : "gitlab-self-hosted" : configuredProvider === "other" ? "custom" : "known-other");
1922
+ return {
1923
+ provider: configuredProvider,
1924
+ providerKind: configuredKind,
1925
+ confidence: "high",
1926
+ evidence: [{ source: "configuration", value: ".cursor/agent-kit.config.json#git.provider" }]
1927
+ };
1928
+ }
1929
+ if (!remoteUrl) {
1930
+ return { providerKind: "unknown", confidence: "low", evidence: [] };
1931
+ }
1932
+ const hostname = remoteHostname(remoteUrl);
1933
+ const remoteEvidence = [{ source: "git", value: `remote:${remoteUrl}` }];
1934
+ if (hostname === "github.com") {
1935
+ return {
1936
+ provider: "github",
1937
+ providerKind: "github",
1938
+ confidence: "high",
1939
+ evidence: remoteEvidence
1940
+ };
1941
+ }
1942
+ if (hostname === "gitlab.com") {
1943
+ return {
1944
+ provider: "gitlab",
1945
+ providerKind: "gitlab-saas",
1946
+ confidence: "high",
1947
+ evidence: remoteEvidence
1948
+ };
1949
+ }
1950
+ if (hostname === "bitbucket.org") {
1951
+ return {
1952
+ provider: "bitbucket",
1953
+ providerKind: "known-other",
1954
+ confidence: "high",
1955
+ evidence: remoteEvidence
1956
+ };
1957
+ }
1958
+ if (hostname === "dev.azure.com" || hostname?.endsWith(".visualstudio.com")) {
1959
+ return {
1960
+ provider: "azure-devops",
1961
+ providerKind: "known-other",
1962
+ confidence: "high",
1963
+ evidence: remoteEvidence
1964
+ };
1965
+ }
1966
+ if (hostname === "codeberg.org") {
1967
+ return {
1968
+ provider: "gitea",
1969
+ providerKind: "known-other",
1970
+ confidence: "high",
1971
+ evidence: remoteEvidence
1972
+ };
1973
+ }
1974
+ if (await fileExists(path11.join(rootDir, ".gitlab-ci.yml"))) {
1975
+ return {
1976
+ provider: "gitlab",
1977
+ providerKind: "gitlab-self-hosted",
1978
+ confidence: "medium",
1979
+ evidence: [...remoteEvidence, { source: "file", value: ".gitlab-ci.yml" }]
1980
+ };
1981
+ }
1982
+ return {
1983
+ provider: "other",
1984
+ providerKind: hostname ? "custom" : "unknown",
1985
+ confidence: "low",
1986
+ evidence: remoteEvidence
1987
+ };
1988
+ }
1989
+ function inferWorkflow(currentBranch) {
1990
+ if (!currentBranch) return "unknown";
1991
+ if (currentBranch === "main" || currentBranch === "master") return "feature-pr";
1992
+ if (currentBranch.includes("develop") || currentBranch.includes("release")) return "gitflow";
1993
+ if (currentBranch.includes("staging") || currentBranch.includes("homolog")) return "homolog-prod";
1994
+ return "feature-pr";
1995
+ }
1996
+ async function runGit(args, rootDir) {
1997
+ try {
1998
+ const { stdout } = await exec("git", args, { cwd: rootDir });
1999
+ return stdout.trim();
2000
+ } catch {
2001
+ return void 0;
2002
+ }
2003
+ }
2004
+ async function listTrackedFiles(rootDir) {
2005
+ return (await runGit(["ls-files"], rootDir))?.split("\n").filter(Boolean) ?? [];
2006
+ }
2007
+ async function detectGit(rootDir) {
2008
+ const isGit = await runGit(["rev-parse", "--is-inside-work-tree"], rootDir) === "true";
2009
+ if (!isGit) {
2010
+ return {
2011
+ providerKind: "unknown",
2012
+ providerConfidence: "low",
2013
+ providerEvidence: [],
2014
+ remotes: [],
2015
+ mode: "none",
2016
+ workflow: "unknown",
2017
+ isDirty: false,
2018
+ hasLocalStaging: false,
2019
+ hasRemoteStaging: false
2020
+ };
2021
+ }
2022
+ const remoteNames = (await runGit(["remote"], rootDir))?.split("\n").filter(Boolean) ?? [];
2023
+ const remotes = (await Promise.all(
2024
+ remoteNames.map(async (name) => {
2025
+ const url = await runGit(["remote", "get-url", name], rootDir);
2026
+ return url ? { name, url: sanitizeRemoteUrl(url) } : void 0;
2027
+ })
2028
+ )).filter((remote) => remote !== void 0);
2029
+ const primary = remotes.find((remote) => remote.name === "origin") ?? remotes[0];
2030
+ const remoteUrl = primary?.url;
2031
+ const [currentBranch, remoteHead, localBranchOutput, remoteBranchOutput, status, provider] = await Promise.all([
2032
+ runGit(["branch", "--show-current"], rootDir),
2033
+ primary ? runGit(["symbolic-ref", "--short", `refs/remotes/${primary.name}/HEAD`], rootDir) : Promise.resolve(void 0),
2034
+ runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"], rootDir),
2035
+ runGit(["for-each-ref", "--format=%(refname:short)", "refs/remotes"], rootDir),
2036
+ runGit(["status", "--porcelain"], rootDir),
2037
+ detectProvider(rootDir, remoteUrl)
2038
+ ]);
2039
+ const localBranches = localBranchOutput?.split("\n").filter(Boolean);
2040
+ const remoteBranches = remoteBranchOutput?.split("\n").filter(Boolean);
2041
+ const defaultBranch = remoteHead?.split("/").slice(1).join("/") || (localBranches?.includes("main") ? "main" : localBranches?.includes("master") ? "master" : void 0);
2042
+ return {
2043
+ provider: provider.provider,
2044
+ providerKind: provider.providerKind,
2045
+ providerConfidence: provider.confidence,
2046
+ providerEvidence: provider.evidence,
2047
+ remoteUrl,
2048
+ remoteName: primary?.name,
2049
+ remotes,
2050
+ mode: remotes.length > 0 ? "remote-hosted" : "local-only",
2051
+ currentBranch,
2052
+ defaultBranch,
2053
+ isDirty: Boolean(status),
2054
+ hasLocalStaging: localBranches?.includes("staging") ?? false,
2055
+ hasRemoteStaging: remoteBranches?.some(
2056
+ (branch) => branch === "origin/staging" || branch.endsWith("/staging")
2057
+ ) ?? false,
2058
+ workflow: inferWorkflow(currentBranch)
2059
+ };
2060
+ }
2061
+
2062
+ // src/scanner/detect-ide.ts
2063
+ import path12 from "path";
2064
+ async function detectIde(rootDir) {
2065
+ const hasCursor = await fileExists(path12.join(rootDir, ".cursor"));
2066
+ const hasVSCode = await fileExists(path12.join(rootDir, ".vscode"));
2067
+ const hasWindsurf = await fileExists(path12.join(rootDir, ".windsurfrules"));
2068
+ if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
2069
+ if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
2070
+ if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
2071
+ return { ide: "unknown", plan: "default" };
2072
+ }
2073
+
2074
+ // src/scanner/detect-infra.ts
2075
+ import path13 from "path";
2076
+
1375
2077
  // src/types.ts
1376
- var GIT_PLATFORM_META = {
1377
- github: {
1378
- cli: "gh",
1379
- prTerm: "Pull Request",
1380
- prCommand: "gh pr create",
1381
- ciDefault: "github-actions"
1382
- },
1383
- gitlab: {
1384
- cli: "glab",
1385
- prTerm: "Merge Request",
1386
- prCommand: "glab mr create",
1387
- ciDefault: "gitlab-ci"
1388
- },
1389
- bitbucket: {
1390
- cli: "bb",
1391
- prTerm: "Pull Request",
1392
- prCommand: "bb pr create",
1393
- ciDefault: "bitbucket-pipelines"
1394
- },
1395
- "azure-devops": {
1396
- cli: "az repos",
1397
- prTerm: "Pull Request",
1398
- prCommand: "az repos pr create",
1399
- ciDefault: "azure-pipelines"
1400
- },
1401
- gitea: { cli: "tea", prTerm: "Pull Request", prCommand: "tea pr create", ciDefault: "none" }
1402
- };
1403
2078
  var CI_PLATFORM_FILES = {
1404
2079
  "github-actions": ".github/workflows",
1405
2080
  "gitlab-ci": ".gitlab-ci.yml",
@@ -1424,48 +2099,625 @@ var PM_TOOL_LABELS = {
1424
2099
  none: "None"
1425
2100
  };
1426
2101
 
1427
- // src/commands/handoff.ts
1428
- function parsePlanFrontmatter(raw) {
1429
- const match = raw.match(/^---\n([\s\S]*?)\n---/);
1430
- if (!match?.[1]) return null;
1431
- const block = match[1];
1432
- const name = block.match(/^name:\s*(.+)$/m)?.[1]?.trim();
1433
- const todos = [];
1434
- const todoBlocks = block.matchAll(/- id:\s*(\S+)\s*\n\s*content:\s*(.+)\n\s*status:\s*(\S+)/g);
1435
- for (const m of todoBlocks) {
1436
- if (m[1] && m[2] && m[3]) {
1437
- todos.push({ id: m[1], content: m[2].replace(/^["']|["']$/g, ""), status: m[3] });
2102
+ // src/scanner/detect-infra.ts
2103
+ async function detectInfra(rootDir) {
2104
+ const docker = await fileExists(path13.join(rootDir, "Dockerfile")) || await fileExists(path13.join(rootDir, "docker-compose.yml")) || await fileExists(path13.join(rootDir, "docker-compose.yaml"));
2105
+ const kubernetes = await fileExists(path13.join(rootDir, "k8s")) || await fileExists(path13.join(rootDir, "kubernetes"));
2106
+ let ci = "none";
2107
+ const ciFiles = [];
2108
+ for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
2109
+ if (await fileExists(path13.join(rootDir, filePath))) {
2110
+ if (ci === "none") ci = platform;
2111
+ ciFiles.push(filePath);
1438
2112
  }
1439
2113
  }
1440
- return { name, todos };
2114
+ const infrastructureCandidates = [
2115
+ "Dockerfile",
2116
+ "docker-compose.yml",
2117
+ "docker-compose.yaml",
2118
+ "k8s",
2119
+ "kubernetes",
2120
+ "terraform",
2121
+ "infra"
2122
+ ];
2123
+ const deploymentCandidates = [
2124
+ "vercel.json",
2125
+ "netlify.toml",
2126
+ "fly.toml",
2127
+ "render.yaml",
2128
+ "Procfile",
2129
+ "deploy",
2130
+ "scripts/deploy.sh"
2131
+ ];
2132
+ const infrastructureFiles = (await Promise.all(
2133
+ infrastructureCandidates.map(
2134
+ async (file) => await fileExists(path13.join(rootDir, file)) ? file : void 0
2135
+ )
2136
+ )).filter((file) => file !== void 0);
2137
+ const deploymentFiles = (await Promise.all(
2138
+ deploymentCandidates.map(
2139
+ async (file) => await fileExists(path13.join(rootDir, file)) ? file : void 0
2140
+ )
2141
+ )).filter((file) => file !== void 0);
2142
+ return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
1441
2143
  }
1442
- async function findActivePlan(plansDir) {
1443
- if (!await fileExists(plansDir)) return null;
1444
- const files = (await readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
1445
- for (const file of files) {
1446
- const raw = await readFile6(path10.join(plansDir, file), "utf8");
1447
- const fm = parsePlanFrontmatter(raw);
1448
- if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
1449
- return { file, raw };
2144
+
2145
+ // src/scanner/detect-services.ts
2146
+ import { readFile as readFile7 } from "fs/promises";
2147
+ import path14 from "path";
2148
+ async function detectProjectManagement(rootDir) {
2149
+ const tools = [];
2150
+ const mcpConfigPaths = [
2151
+ path14.join(rootDir, ".cursor", "mcp.json"),
2152
+ path14.join(rootDir, "mcp.json")
2153
+ ];
2154
+ for (const configPath of mcpConfigPaths) {
2155
+ if (!await fileExists(configPath)) continue;
2156
+ try {
2157
+ const raw = await readFile7(configPath, "utf8");
2158
+ const lower = raw.toLowerCase();
2159
+ if (lower.includes("clickup")) tools.push("clickup");
2160
+ if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
2161
+ if (lower.includes("linear")) tools.push("linear");
2162
+ if (lower.includes("asana")) tools.push("asana");
2163
+ if (lower.includes("youtrack")) tools.push("youtrack");
2164
+ if (lower.includes("shortcut")) tools.push("shortcut");
2165
+ } catch {
1450
2166
  }
1451
2167
  }
1452
- return files[0] ? { file: files[0], raw: await readFile6(path10.join(plansDir, files[0]), "utf8") } : null;
1453
- }
1454
- function now() {
1455
- return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
1456
- }
1457
- async function loadProfile(rootDir) {
1458
- const configPath = path10.join(rootDir, ".cursor", "agent-kit.config.json");
1459
- try {
1460
- return await readJson(configPath);
1461
- } catch {
1462
- return null;
2168
+ if (await fileExists(path14.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
2169
+ tools.push("github-issues");
2170
+ }
2171
+ if (await fileExists(path14.join(rootDir, ".github", "projects"))) {
2172
+ tools.push("github-projects");
1463
2173
  }
2174
+ return [...new Set(tools)];
1464
2175
  }
1465
- function buildRoutines(profile) {
1466
- const lines = [];
1467
- const workflow = profile?.git.workflow;
1468
- if (workflow === "homolog-prod") {
2176
+ async function detectServices(rootDir) {
2177
+ const hasPrisma = await fileExists(path14.join(rootDir, "prisma/schema.prisma"));
2178
+ const hasSequelize = await fileExists(path14.join(rootDir, "sequelize"));
2179
+ const hasDrizzle = await fileExists(path14.join(rootDir, "drizzle.config.ts"));
2180
+ const hasKnex = await fileExists(path14.join(rootDir, "knexfile.ts"));
2181
+ const hasTypeorm = await fileExists(path14.join(rootDir, "ormconfig.json"));
2182
+ const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
2183
+ const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
2184
+ const projectManagement = await detectProjectManagement(rootDir);
2185
+ return {
2186
+ database,
2187
+ orm,
2188
+ projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2189
+ };
2190
+ }
2191
+
2192
+ // src/scanner/detect-stack.ts
2193
+ import path15 from "path";
2194
+ var PROJECT_MARKERS = [
2195
+ "package.json",
2196
+ "requirements.txt",
2197
+ "pyproject.toml",
2198
+ "go.mod",
2199
+ "Gemfile",
2200
+ "pom.xml",
2201
+ "build.gradle",
2202
+ "composer.json",
2203
+ "Cargo.toml",
2204
+ "pnpm-workspace.yaml"
2205
+ ];
2206
+ var LOCKFILES = [
2207
+ ["pnpm-lock.yaml", "pnpm"],
2208
+ ["yarn.lock", "yarn"],
2209
+ ["bun.lockb", "bun"],
2210
+ ["bun.lock", "bun"],
2211
+ ["package-lock.json", "npm"]
2212
+ ];
2213
+ async function detectPackageManager(rootDir, packageJson) {
2214
+ const configured = packageJson.packageManager?.split("@")[0];
2215
+ if (configured && ["npm", "pnpm", "yarn", "bun"].includes(configured)) {
2216
+ return {
2217
+ packageManager: configured,
2218
+ evidence: [{ source: "configuration", value: `package.json#packageManager=${configured}` }]
2219
+ };
2220
+ }
2221
+ for (const [lockfile, packageManager] of LOCKFILES) {
2222
+ if (await fileExists(path15.join(rootDir, lockfile))) {
2223
+ return {
2224
+ packageManager,
2225
+ evidence: [{ source: "file", value: lockfile }]
2226
+ };
2227
+ }
2228
+ }
2229
+ return { evidence: [] };
2230
+ }
2231
+ function commandsForScripts(scripts, packageManager) {
2232
+ const runner = packageManager ?? "npm";
2233
+ const command = (name) => runner === "npm" ? `npm run ${name}` : `${runner} ${name}`;
2234
+ const testCommands = Object.keys(scripts).filter((name) => name === "test" || name.startsWith("test:")).map(command);
2235
+ const validationCommands = ["lint", "typecheck", "check", "build"].filter((name) => scripts[name]).map(command);
2236
+ return { testCommands, validationCommands };
2237
+ }
2238
+ async function detectStack(rootDir) {
2239
+ const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path15.join(rootDir, item))))).some(Boolean);
2240
+ const hasPackageJson = await fileExists(path15.join(rootDir, "package.json"));
2241
+ if (hasPackageJson) {
2242
+ const packageJson = await readJson(path15.join(rootDir, "package.json")) ?? {};
2243
+ const scripts = packageJson.scripts ?? {};
2244
+ const packageManager = await detectPackageManager(rootDir, packageJson);
2245
+ const commands = commandsForScripts(scripts, packageManager.packageManager);
2246
+ const hasNextConfig = await fileExists(path15.join(rootDir, "next.config.js")) || await fileExists(path15.join(rootDir, "next.config.mjs")) || await fileExists(path15.join(rootDir, "next.config.ts"));
2247
+ const hasNestConfig = await fileExists(path15.join(rootDir, "nest-cli.json"));
2248
+ return {
2249
+ language: "node",
2250
+ framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
2251
+ packageManager: packageManager.packageManager,
2252
+ packageManagerEvidence: packageManager.evidence,
2253
+ scripts,
2254
+ workspaces: packageJson.workspaces !== void 0 || await fileExists(path15.join(rootDir, "pnpm-workspace.yaml")),
2255
+ ...commands,
2256
+ hasProjectFiles: hasAnyProjectMarker
2257
+ };
2258
+ }
2259
+ if (await fileExists(path15.join(rootDir, "pyproject.toml"))) {
2260
+ return {
2261
+ language: "python",
2262
+ framework: "python",
2263
+ workspaces: false,
2264
+ testCommands: [],
2265
+ validationCommands: [],
2266
+ hasProjectFiles: hasAnyProjectMarker
2267
+ };
2268
+ }
2269
+ if (await fileExists(path15.join(rootDir, "go.mod"))) {
2270
+ return {
2271
+ language: "go",
2272
+ framework: "go",
2273
+ workspaces: false,
2274
+ testCommands: ["go test ./..."],
2275
+ validationCommands: [],
2276
+ hasProjectFiles: hasAnyProjectMarker
2277
+ };
2278
+ }
2279
+ if (await fileExists(path15.join(rootDir, "Cargo.toml"))) {
2280
+ return {
2281
+ language: "rust",
2282
+ framework: "rust",
2283
+ workspaces: false,
2284
+ testCommands: ["cargo test"],
2285
+ validationCommands: ["cargo check"],
2286
+ hasProjectFiles: hasAnyProjectMarker
2287
+ };
2288
+ }
2289
+ if (await fileExists(path15.join(rootDir, "composer.json"))) {
2290
+ return {
2291
+ language: "php",
2292
+ framework: "php",
2293
+ workspaces: false,
2294
+ testCommands: [],
2295
+ validationCommands: [],
2296
+ hasProjectFiles: hasAnyProjectMarker
2297
+ };
2298
+ }
2299
+ return {
2300
+ language: "unknown",
2301
+ workspaces: false,
2302
+ testCommands: [],
2303
+ validationCommands: [],
2304
+ hasProjectFiles: hasAnyProjectMarker
2305
+ };
2306
+ }
2307
+
2308
+ // src/scanner/scan.ts
2309
+ var GREENFIELD_SAFE_FILES = /* @__PURE__ */ new Set([
2310
+ ".git",
2311
+ ".gitignore",
2312
+ "LICENSE",
2313
+ "README.md",
2314
+ ".cursor",
2315
+ ".vscode"
2316
+ ]);
2317
+ function isGreenfieldByEntries(entries) {
2318
+ const meaningful = entries.filter((entry) => !GREENFIELD_SAFE_FILES.has(entry));
2319
+ return meaningful.length === 0;
2320
+ }
2321
+ async function runScanner(rootDir) {
2322
+ const normalizedRoot = path16.resolve(rootDir);
2323
+ const entries = await listDirectory(normalizedRoot);
2324
+ const stack = await detectStack(normalizedRoot);
2325
+ const purpose = await detectPurpose(normalizedRoot, stack);
2326
+ const [git, ide, infra, services, context, agentKit, trackedFiles] = await Promise.all([
2327
+ detectGit(normalizedRoot),
2328
+ detectIde(normalizedRoot),
2329
+ detectInfra(normalizedRoot),
2330
+ detectServices(normalizedRoot),
2331
+ detectContext(normalizedRoot),
2332
+ detectAgentKit(normalizedRoot),
2333
+ listTrackedFiles(normalizedRoot)
2334
+ ]);
2335
+ const safety = await detectSafety(normalizedRoot, trackedFiles);
2336
+ const isGreenfield = isGreenfieldByEntries(entries) && purpose.value === "unknown";
2337
+ return {
2338
+ rootDir: normalizedRoot,
2339
+ isGreenfield,
2340
+ purpose,
2341
+ stack,
2342
+ git,
2343
+ ide,
2344
+ infra,
2345
+ services,
2346
+ context,
2347
+ agentKit,
2348
+ safety,
2349
+ quality: {
2350
+ testCommands: stack.testCommands,
2351
+ validationCommands: stack.validationCommands,
2352
+ ci: infra.ci,
2353
+ hasTests: stack.testCommands.length > 0
2354
+ }
2355
+ };
2356
+ }
2357
+
2358
+ // src/scanner/safe-fixes.ts
2359
+ var PROFILE_RELATIVE_PATH = ".cursor/agent-kit.config.json";
2360
+ var CONTEXT_CONFIG_RELATIVE_PATH = ".cursor/context/config.json";
2361
+ var ESSENTIAL_DIRECTORIES = [
2362
+ ".cursor",
2363
+ ".cursor/context",
2364
+ ".cursor/context/current",
2365
+ ".cursor/context/backups",
2366
+ ".cursor/plans",
2367
+ ".cursor/memory"
2368
+ ];
2369
+ function isObject(value) {
2370
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2371
+ }
2372
+ function mergeMissing(existing, defaults) {
2373
+ if (!isObject(existing) || !isObject(defaults)) return existing ?? defaults;
2374
+ const merged = { ...existing };
2375
+ for (const [key, value] of Object.entries(defaults)) {
2376
+ merged[key] = key in existing ? mergeMissing(existing[key], value) : value;
2377
+ }
2378
+ return merged;
2379
+ }
2380
+ function jsonEqual(left, right) {
2381
+ return JSON.stringify(left) === JSON.stringify(right);
2382
+ }
2383
+ function relativeEvidence(relativePath, detail) {
2384
+ return [`${relativePath}: ${detail}`];
2385
+ }
2386
+ function createProfile(scan, report, generatedAt) {
2387
+ const git = {
2388
+ mode: scan.git.mode,
2389
+ workflow: scan.git.workflow,
2390
+ remotes: scan.git.remotes,
2391
+ remoteUrl: scan.git.remoteUrl,
2392
+ remoteName: scan.git.remoteName,
2393
+ currentBranch: scan.git.currentBranch,
2394
+ defaultBranch: scan.git.defaultBranch,
2395
+ isDirty: scan.git.isDirty,
2396
+ hasLocalStaging: scan.git.hasLocalStaging,
2397
+ hasRemoteStaging: scan.git.hasRemoteStaging
2398
+ };
2399
+ if (scan.git.providerConfidence === "high") {
2400
+ git.provider = scan.git.provider;
2401
+ git.providerKind = scan.git.providerKind;
2402
+ git.providerConfidence = scan.git.providerConfidence;
2403
+ git.providerEvidence = scan.git.providerEvidence;
2404
+ }
2405
+ return {
2406
+ schemaVersion: 1,
2407
+ contractVersion: 1,
2408
+ purpose: scan.purpose,
2409
+ stack: scan.stack,
2410
+ git,
2411
+ infra: scan.infra,
2412
+ services: scan.services,
2413
+ context: scan.context,
2414
+ detection: {
2415
+ generatedAt,
2416
+ repositoryFingerprint: report.repositoryFingerprint,
2417
+ providerConfidence: scan.git.providerConfidence,
2418
+ providerEvidence: scan.git.providerEvidence
2419
+ }
2420
+ };
2421
+ }
2422
+ function createOnboardingState(report, generatedAt, deferredItems = report.deferredChecks) {
2423
+ const validDeferredCheckIds = new Set(
2424
+ deferredItems.filter((item) => item.reason.trim().length > 0).map((item) => item.checkId)
2425
+ );
2426
+ const unresolvedEssential = report.pillars.flatMap((pillar2) => pillar2.checks).some(
2427
+ (check2) => check2.essential && check2.status !== "ready" && (check2.status === "blocked" || !validDeferredCheckIds.has(check2.id))
2428
+ );
2429
+ return {
2430
+ contractVersion: 1,
2431
+ status: unresolvedEssential ? "in_progress" : "completed",
2432
+ updatedAt: generatedAt,
2433
+ checks: Object.fromEntries(
2434
+ report.pillars.flatMap((pillar2) => pillar2.checks).map((check2) => [
2435
+ check2.id,
2436
+ {
2437
+ status: check2.status,
2438
+ essential: check2.essential,
2439
+ evidence: check2.evidence
2440
+ }
2441
+ ])
2442
+ ),
2443
+ deferredItems
2444
+ };
2445
+ }
2446
+ function validDeferredItems(value, fallback) {
2447
+ if (!Array.isArray(value)) return fallback;
2448
+ return value.filter(
2449
+ (item) => isObject(item) && typeof item.checkId === "string" && typeof item.reason === "string" && (item.recoveryCommand === void 0 || typeof item.recoveryCommand === "string")
2450
+ );
2451
+ }
2452
+ function reconcileOnboardingState(report, existingConfig, generatedAt) {
2453
+ const existing = isObject(existingConfig.onboarding) ? existingConfig.onboarding : {};
2454
+ const deferredItems = validDeferredItems(existing.deferredItems, report.deferredChecks);
2455
+ const derived = createOnboardingState(report, generatedAt, deferredItems);
2456
+ const candidate2 = {
2457
+ ...existing,
2458
+ ...derived
2459
+ };
2460
+ const { updatedAt: _candidateUpdatedAt, ...candidateState } = candidate2;
2461
+ const { updatedAt: existingUpdatedAt, ...existingState } = existing;
2462
+ candidate2.updatedAt = jsonEqual(candidateState, existingState) && typeof existingUpdatedAt === "string" ? existingUpdatedAt : generatedAt;
2463
+ return candidate2;
2464
+ }
2465
+ function preferenceDefaults(onboarding, onboarded) {
2466
+ return {
2467
+ onboarded: onboarded === true,
2468
+ onboarding,
2469
+ externalPlanReview: {
2470
+ enabled: false,
2471
+ backend: "claude",
2472
+ autoRemediate: false,
2473
+ offerOnExhausted: true
2474
+ },
2475
+ autoHandoff: false,
2476
+ workspaceSkin: {
2477
+ default: "autopilot",
2478
+ modes: {
2479
+ "continue-plan": "autopilot",
2480
+ "run-plan": "night-shift",
2481
+ "cli-run-plan": "ghost-runner"
2482
+ }
2483
+ }
2484
+ };
2485
+ }
2486
+ function mergeSecretIgnores(existing) {
2487
+ const activeLines = new Set(
2488
+ existing.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"))
2489
+ );
2490
+ const missing = REQUIRED_SECRET_PATTERNS.filter((pattern) => !activeLines.has(pattern));
2491
+ if (missing.length === 0) return existing;
2492
+ const prefix = existing.length === 0 ? "" : existing.endsWith("\n") ? existing : `${existing}
2493
+ `;
2494
+ return `${prefix}${missing.join("\n")}
2495
+ `;
2496
+ }
2497
+ function recordChange(changes, id, relativePath, changed, dryRun, evidence) {
2498
+ changes.push({
2499
+ id,
2500
+ path: relativePath,
2501
+ status: changed ? dryRun ? "planned" : "applied" : "skipped",
2502
+ evidence
2503
+ });
2504
+ }
2505
+ function appliedActions(changes) {
2506
+ return changes.filter((change) => change.status === "applied").map((change) => ({
2507
+ id: change.id,
2508
+ status: "ready",
2509
+ recommendation: `Applied safe local change to ${change.path}`,
2510
+ owner: "system"
2511
+ }));
2512
+ }
2513
+ async function executeSafeReadinessFixes(rootDir, options) {
2514
+ const dryRun = options.dryRun ?? false;
2515
+ const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2516
+ const beforeScan = await runScanner(rootDir);
2517
+ const before = createReadinessReport(beforeScan, {
2518
+ generatorVersion: options.generatorVersion,
2519
+ generatedAt
2520
+ });
2521
+ const changes = [];
2522
+ for (const relativePath of ESSENTIAL_DIRECTORIES) {
2523
+ const absolutePath = path17.join(beforeScan.rootDir, relativePath);
2524
+ const exists = await fileExists(absolutePath);
2525
+ if (!exists && !dryRun) await ensureDir(absolutePath);
2526
+ recordChange(
2527
+ changes,
2528
+ "ensure-agent-kit-directory",
2529
+ relativePath,
2530
+ !exists,
2531
+ dryRun,
2532
+ relativeEvidence(relativePath, exists ? "already exists" : "missing directory")
2533
+ );
2534
+ }
2535
+ const gitignoreRelativePath = ".gitignore";
2536
+ const gitignorePath = path17.join(beforeScan.rootDir, gitignoreRelativePath);
2537
+ const existingGitignore = await fileExists(gitignorePath) ? await readFile8(gitignorePath, "utf8") : "";
2538
+ const mergedGitignore = mergeSecretIgnores(existingGitignore);
2539
+ const gitignoreChanged = mergedGitignore !== existingGitignore;
2540
+ if (gitignoreChanged && !dryRun) await writeFile3(gitignorePath, mergedGitignore, "utf8");
2541
+ recordChange(
2542
+ changes,
2543
+ "merge-secret-ignores",
2544
+ gitignoreRelativePath,
2545
+ gitignoreChanged,
2546
+ dryRun,
2547
+ relativeEvidence(
2548
+ gitignoreRelativePath,
2549
+ gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
2550
+ )
2551
+ );
2552
+ const profilePath = path17.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
2553
+ const existingProfile = await readJson(profilePath) ?? {};
2554
+ const desiredProfile = createProfile(beforeScan, before, generatedAt);
2555
+ const mergedProfile = mergeMissing(existingProfile, desiredProfile);
2556
+ const profileChanged = !jsonEqual(existingProfile, mergedProfile);
2557
+ if (profileChanged && !dryRun) await writeJson(profilePath, mergedProfile);
2558
+ recordChange(
2559
+ changes,
2560
+ "merge-repository-profile",
2561
+ PROFILE_RELATIVE_PATH,
2562
+ profileChanged,
2563
+ dryRun,
2564
+ relativeEvidence(
2565
+ PROFILE_RELATIVE_PATH,
2566
+ profileChanged ? "missing scanner-derived facts" : "existing facts preserved"
2567
+ )
2568
+ );
2569
+ const evidenceScan = dryRun ? beforeScan : await runScanner(beforeScan.rootDir);
2570
+ const evidenceReport = createReadinessReport(evidenceScan, {
2571
+ generatorVersion: options.generatorVersion,
2572
+ generatedAt
2573
+ });
2574
+ const contextConfigPath = path17.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
2575
+ const existingContextConfig = await readJson(contextConfigPath) ?? {};
2576
+ const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
2577
+ const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
2578
+ const mergedContextConfig = mergeMissing(existingContextConfig, defaults);
2579
+ mergedContextConfig.onboarding = onboarding;
2580
+ const contextConfigChanged = !jsonEqual(existingContextConfig, mergedContextConfig);
2581
+ if (contextConfigChanged && !dryRun) await writeJson(contextConfigPath, mergedContextConfig);
2582
+ recordChange(
2583
+ changes,
2584
+ "merge-onboarding-state",
2585
+ CONTEXT_CONFIG_RELATIVE_PATH,
2586
+ contextConfigChanged,
2587
+ dryRun,
2588
+ relativeEvidence(
2589
+ CONTEXT_CONFIG_RELATIVE_PATH,
2590
+ contextConfigChanged ? "missing preferences or onboarding state" : "state already merged"
2591
+ )
2592
+ );
2593
+ const afterScan = dryRun ? beforeScan : await runScanner(beforeScan.rootDir);
2594
+ const after = createReadinessReport(afterScan, {
2595
+ generatorVersion: options.generatorVersion,
2596
+ generatedAt
2597
+ });
2598
+ after.appliedSafeFixes = appliedActions(changes);
2599
+ return { dryRun, changes, before, after };
2600
+ }
2601
+
2602
+ // src/scanner/snapshot.ts
2603
+ import path18 from "path";
2604
+ var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
2605
+ async function writeReadinessSnapshot(rootDir, report) {
2606
+ const snapshotPath = path18.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
2607
+ await writeJson(snapshotPath, report);
2608
+ return snapshotPath;
2609
+ }
2610
+
2611
+ // src/commands/doctor.ts
2612
+ async function runDoctor(cwd, options = {}) {
2613
+ const rootDir = path19.resolve(cwd);
2614
+ if (options.fixSafe) {
2615
+ const execution = await executeSafeReadinessFixes(rootDir, {
2616
+ generatorVersion: KIT_VERSION,
2617
+ generatedAt: options.generatedAt
2618
+ });
2619
+ await writeReadinessSnapshot(rootDir, execution.after);
2620
+ return { report: execution.after, safeChanges: execution.changes };
2621
+ }
2622
+ const scan = await runScanner(rootDir);
2623
+ const report = createReadinessReport(scan, {
2624
+ generatorVersion: KIT_VERSION,
2625
+ generatedAt: options.generatedAt
2626
+ });
2627
+ await writeReadinessSnapshot(rootDir, report);
2628
+ return { report, safeChanges: [] };
2629
+ }
2630
+ function printDoctorSummary(result) {
2631
+ const { summary, pendingActions } = result.report;
2632
+ const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
2633
+ const nextAction = pendingActions[0];
2634
+ console.log("Repository readiness");
2635
+ console.log(
2636
+ ` ready: ${summary.ready}, choices: ${summary.needs_choice}, manual: ${summary.manual}, blocked: ${summary.blocked}`
2637
+ );
2638
+ console.log(` safe fixes applied: ${fixed}`);
2639
+ console.log(` pending actions: ${pendingActions.length}`);
2640
+ console.log(
2641
+ nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
2642
+ );
2643
+ }
2644
+ var doctorCommand = defineCommand4({
2645
+ meta: {
2646
+ name: "doctor",
2647
+ description: "Diagnose repository readiness and optionally apply safe local fixes."
2648
+ },
2649
+ args: {
2650
+ cwd: {
2651
+ type: "string",
2652
+ default: process.cwd()
2653
+ },
2654
+ json: {
2655
+ type: "boolean",
2656
+ description: "Print deterministic machine-readable JSON without status messages",
2657
+ default: false
2658
+ },
2659
+ "fix-safe": {
2660
+ type: "boolean",
2661
+ description: "Apply only local, reversible, merge-safe readiness fixes",
2662
+ default: false
2663
+ }
2664
+ },
2665
+ async run({ args }) {
2666
+ const result = await runDoctor(args.cwd, { fixSafe: args["fix-safe"] });
2667
+ if (args.json) {
2668
+ console.log(JSON.stringify(result, null, 2));
2669
+ return;
2670
+ }
2671
+ printDoctorSummary(result);
2672
+ }
2673
+ });
2674
+
2675
+ // src/commands/handoff.ts
2676
+ import { spawn } from "child_process";
2677
+ import { readFile as readFile9, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
2678
+ import path20 from "path";
2679
+ import { defineCommand as defineCommand5 } from "citty";
2680
+ function parsePlanFrontmatter(raw) {
2681
+ const match = raw.match(/^---\n([\s\S]*?)\n---/);
2682
+ if (!match?.[1]) return null;
2683
+ const block = match[1];
2684
+ const name = block.match(/^name:\s*(.+)$/m)?.[1]?.trim();
2685
+ const todos = [];
2686
+ const todoBlocks = block.matchAll(/- id:\s*(\S+)\s*\n\s*content:\s*(.+)\n\s*status:\s*(\S+)/g);
2687
+ for (const m of todoBlocks) {
2688
+ if (m[1] && m[2] && m[3]) {
2689
+ todos.push({ id: m[1], content: m[2].replace(/^["']|["']$/g, ""), status: m[3] });
2690
+ }
2691
+ }
2692
+ return { name, todos };
2693
+ }
2694
+ async function findActivePlan(plansDir) {
2695
+ if (!await fileExists(plansDir)) return null;
2696
+ const files = (await readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
2697
+ for (const file of files) {
2698
+ const raw = await readFile9(path20.join(plansDir, file), "utf8");
2699
+ const fm = parsePlanFrontmatter(raw);
2700
+ if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
2701
+ return { file, raw };
2702
+ }
2703
+ }
2704
+ return files[0] ? { file: files[0], raw: await readFile9(path20.join(plansDir, files[0]), "utf8") } : null;
2705
+ }
2706
+ function now() {
2707
+ return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
2708
+ }
2709
+ async function loadProfile(rootDir) {
2710
+ const configPath = path20.join(rootDir, ".cursor", "agent-kit.config.json");
2711
+ try {
2712
+ return await readJson(configPath);
2713
+ } catch {
2714
+ return null;
2715
+ }
2716
+ }
2717
+ function buildRoutines(profile) {
2718
+ const lines = [];
2719
+ const workflow = profile?.git.workflow;
2720
+ if (workflow === "homolog-prod") {
1469
2721
  lines.push("- [ ] `git staging` - move changes to staging");
1470
2722
  lines.push("- [ ] `git prod` - promote to production (after approval)");
1471
2723
  } else {
@@ -1541,7 +2793,7 @@ function runCursorHandoff(scriptPath, cwd) {
1541
2793
  child.on("close", (code) => resolve(code ?? 1));
1542
2794
  });
1543
2795
  }
1544
- var handoffCommand = defineCommand4({
2796
+ var handoffCommand = defineCommand5({
1545
2797
  meta: {
1546
2798
  name: "handoff",
1547
2799
  description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
@@ -1555,15 +2807,15 @@ var handoffCommand = defineCommand4({
1555
2807
  },
1556
2808
  async run({ args }) {
1557
2809
  const profile = await loadProfile(args.cwd);
1558
- const plansDir = path10.join(args.cwd, ".cursor", "plans");
1559
- const handoffPath = path10.join(args.cwd, ".cursor", "HANDOFF.md");
2810
+ const plansDir = path20.join(args.cwd, ".cursor", "plans");
2811
+ const handoffPath = path20.join(args.cwd, ".cursor", "HANDOFF.md");
1560
2812
  const plan = await findActivePlan(plansDir);
1561
2813
  if (plan) {
1562
2814
  const fm = parsePlanFrontmatter(plan.raw);
1563
2815
  if (fm) {
1564
- await ensureDir(path10.join(args.cwd, ".cursor"));
2816
+ await ensureDir(path20.join(args.cwd, ".cursor"));
1565
2817
  const content = buildHandoff(plan.file, fm, profile);
1566
- await writeFile3(handoffPath, content, "utf8");
2818
+ await writeFile4(handoffPath, content, "utf8");
1567
2819
  logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
1568
2820
  logger.info(
1569
2821
  `Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
@@ -1581,7 +2833,7 @@ var handoffCommand = defineCommand4({
1581
2833
  }
1582
2834
  logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
1583
2835
  }
1584
- const scriptPath = path10.join(args.cwd, "cursor-handoff");
2836
+ const scriptPath = path20.join(args.cwd, "cursor-handoff");
1585
2837
  if (!await fileExists(scriptPath)) {
1586
2838
  printV3Guidance();
1587
2839
  return;
@@ -1606,868 +2858,320 @@ var handoffCommand = defineCommand4({
1606
2858
  });
1607
2859
 
1608
2860
  // src/commands/init.ts
1609
- import path23 from "path";
1610
2861
  import { intro, outro } from "@clack/prompts";
1611
- import { defineCommand as defineCommand5 } from "citty";
1612
-
1613
- // src/generator/index.ts
1614
- import { writeFile as writeFile9 } from "fs/promises";
1615
- import path16 from "path";
2862
+ import { defineCommand as defineCommand7 } from "citty";
1616
2863
 
1617
- // src/generator/agents-md.ts
1618
- import { writeFile as writeFile4 } from "fs/promises";
1619
- import path11 from "path";
2864
+ // src/commands/install.ts
2865
+ import path23 from "path";
2866
+ import { defineCommand as defineCommand6 } from "citty";
1620
2867
 
1621
- // src/generator/platform.ts
1622
- function gitProviderLabel(profile) {
1623
- const p = profile.git.provider;
1624
- if (!p || p === "other") return "Git";
1625
- const labels = {
1626
- github: "GitHub",
1627
- gitlab: "GitLab",
1628
- bitbucket: "Bitbucket",
1629
- "azure-devops": "Azure DevOps",
1630
- gitea: "Gitea"
1631
- };
1632
- return labels[p] ?? "Git";
1633
- }
1634
- function gitCliTool(profile) {
1635
- const p = profile.git.provider;
1636
- if (!p || p === "other") return "git";
1637
- return GIT_PLATFORM_META[p]?.cli ?? "git";
1638
- }
1639
- function prTerminology(profile) {
1640
- const p = profile.git.provider;
1641
- if (!p || p === "other") return "PR/MR";
1642
- return GIT_PLATFORM_META[p]?.prTerm ?? "PR/MR";
1643
- }
1644
- function pmToolsList(profile) {
1645
- const tools = profile.services.projectManagement;
1646
- if (!tools || tools.length === 0) return "";
1647
- return tools.map((t) => PM_TOOL_LABELS[t]).join(", ");
1648
- }
1649
- function pmRoutineLine(profile) {
1650
- const label = pmToolsList(profile);
1651
- if (!label) return "";
1652
- return `- Update tasks in ${label} (if applicable)`;
1653
- }
1654
- function ciLabel(profile) {
1655
- const labels = {
1656
- "github-actions": "GitHub Actions",
1657
- "gitlab-ci": "GitLab CI",
1658
- "azure-pipelines": "Azure Pipelines",
1659
- "bitbucket-pipelines": "Bitbucket Pipelines",
1660
- jenkins: "Jenkins",
1661
- circleci: "CircleCI",
1662
- travis: "Travis CI"
1663
- };
1664
- return labels[profile.infra.ci] ?? "";
1665
- }
1666
- function devopsFlowSummary(profile) {
1667
- const parts = [];
1668
- const provider = gitProviderLabel(profile);
1669
- parts.push(`Git: ${provider}`);
1670
- if (profile.git.workflow !== "unknown") {
1671
- const workflowLabels = {
1672
- "trunk-based": "trunk-based",
1673
- "feature-pr": `feature branch \u2192 ${prTerminology(profile)} \u2192 main`,
1674
- gitflow: "gitflow (develop/release/main)",
1675
- "homolog-prod": "staging \u2192 production"
1676
- };
1677
- parts.push(`Workflow: ${workflowLabels[profile.git.workflow] ?? profile.git.workflow}`);
1678
- }
1679
- const ci = ciLabel(profile);
1680
- if (ci) parts.push(`CI/CD: ${ci}`);
1681
- const pm = pmToolsList(profile);
1682
- if (pm) parts.push(`Project mgmt: ${pm}`);
1683
- return parts.join(" | ");
1684
- }
1685
-
1686
- // src/generator/agents-md.ts
1687
- async function generateAgentsMd(profile) {
1688
- const target = path11.join(profile.rootDir, "AGENTS.md");
1689
- const flow = devopsFlowSummary(profile);
1690
- const prTerm = prTerminology(profile);
1691
- const content = `# AGENTS.md
1692
-
1693
- Project configured with Agent Kit v3.
1694
-
1695
- ## Detected context
1696
- - Stack: ${profile.stack.language}${profile.stack.framework ? ` (${profile.stack.framework})` : ""}
1697
- - IDE: ${profile.ide.ide} (${profile.ide.plan})
1698
- - ${flow}
1699
-
1700
- ## Guidelines
1701
- 1. Prefer small, verifiable changes.
1702
- 2. Adapt response depth to user's IDE plan/model.
1703
- 3. Use /worktree for experiments, /best-of-n for critical decisions (Cursor 3.0).
1704
- 4. Security review before merge.
1705
- 5. Always create a ${prTerm} \u2014 never push directly to main.
1706
- `;
1707
- await writeFile4(target, content, "utf8");
2868
+ // src/generator/personalization.ts
2869
+ import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2870
+ import path21 from "path";
2871
+ var PERSONALIZATION_CONTRACT_VERSION = 1;
2872
+ var CONTEXT_PATH = ".cursor/project-context.md";
2873
+ var AGENTS_PATH = "AGENTS.md";
2874
+ var RESULT_PATH = ".cursor/context/personalization.json";
2875
+ function uniqueEvidence(evidence) {
2876
+ const seen = /* @__PURE__ */ new Set();
2877
+ return evidence.filter((item) => {
2878
+ const key = `${item.source}:${item.value}`;
2879
+ if (seen.has(key)) return false;
2880
+ seen.add(key);
2881
+ return true;
2882
+ });
1708
2883
  }
1709
-
1710
- // src/generator/cursor.ts
1711
- import { writeFile as writeFile5 } from "fs/promises";
1712
- import path12 from "path";
1713
- function buildGitWorkflowRule(profile) {
1714
- const prTerm = prTerminology(profile);
1715
- const cli = gitCliTool(profile);
1716
- const provider = gitProviderLabel(profile);
1717
- const pm = pmToolsList(profile);
1718
- const lines = [
1719
- "# Git Workflow",
1720
- `- Platform: ${provider} | CLI: \`${cli}\``,
1721
- "- Use Conventional Commits (feat:, fix:, docs:, refactor:, chore:, perf:, test:).",
1722
- "- For risky changes, prefer /worktree (isolated git worktree).",
1723
- "- For comparing approaches, use /best-of-n."
1724
- ];
1725
- if (profile.git.workflow === "homolog-prod") {
1726
- lines.push(
1727
- "- Flow: development \u2192 staging \u2192 production.",
1728
- `- Create ${prTerm} targeting the staging branch; merge to main only via promotion.`,
1729
- "- NEVER commit directly to main."
1730
- );
1731
- } else if (profile.git.workflow === "feature-pr") {
1732
- lines.push(
1733
- `- Flow: feature branch \u2192 ${prTerm} \u2192 main.`,
1734
- `- Always create a ${prTerm} for review before merging.`
1735
- );
1736
- } else if (profile.git.workflow === "gitflow") {
1737
- lines.push(
1738
- "- Flow: feature \u2192 develop \u2192 release \u2192 main.",
1739
- `- Create ${prTerm} targeting develop for features, main for releases.`
1740
- );
1741
- }
1742
- if (pm) {
1743
- lines.push(`- After merge: update task status in ${pm} if integration is available.`);
1744
- }
1745
- return `${lines.join("\n")}
1746
- `;
2884
+ function purposeEvidence(profile) {
2885
+ if (profile.purpose.confidence === "low") return [];
2886
+ return profile.purpose.evidence;
1747
2887
  }
1748
- function buildHandoffRule(profile) {
1749
- const pm = pmRoutineLine(profile);
1750
- const routines = ["- Suggest routines: git commit/push, review CHANGELOG."];
1751
- if (pm) routines.push(`${pm}.`);
1752
- return `# Handoff \u2014 State in File
1753
- - After completing each task: save .cursor/HANDOFF.md with progress and next steps.
1754
- - Update to-dos in the plan (.cursor/plans/) when completing or starting a task.
1755
- - One HANDOFF per project \u2014 source of truth for continuity.
1756
- ${routines.join("\n")}
1757
- - To resume: /continue-plan in a new conversation.
1758
- - Native features (summaries, /resume, transcripts, Agents Window) complement \u2014 not replace.
1759
- - Context at ~60%: save handoff and suggest new conversation.
1760
- `;
2888
+ function contextEvidence(profile, pattern) {
2889
+ return profile.context.sources.filter((item) => pattern.test(item.value));
1761
2890
  }
1762
- function cursorRulesByPlan(profile) {
1763
- if (profile.ide.plan === "cursor-free") {
1764
- return [
1765
- {
1766
- filename: "01-core.mdc",
1767
- content: `# Core Rule
1768
- - Respond directly and concisely.
1769
- - Focus on small changes and local validation.
1770
- - Size each task for ~50% of context window.
1771
- `
1772
- },
1773
- {
1774
- filename: "02-handoff.mdc",
1775
- content: buildHandoffRule(profile)
1776
- }
1777
- ];
1778
- }
1779
- return [
1780
- {
1781
- filename: "01-core.mdc",
1782
- content: `# Core Rule
1783
- - Execute tasks end-to-end whenever possible.
1784
- - Prioritize security, tests, and architectural consistency.
1785
- - Size each task for ~50% of context window.
1786
- `
1787
- },
1788
- {
1789
- filename: "02-git-workflow.mdc",
1790
- content: buildGitWorkflowRule(profile)
1791
- },
1792
- {
1793
- filename: "03-handoff.mdc",
1794
- content: buildHandoffRule(profile)
1795
- },
1796
- {
1797
- filename: "04-ide-guide.mdc",
1798
- content: `# Cursor Guide
1799
- - Use Agents Window for parallelism \u2014 each agent reads HANDOFF before acting.
1800
- - Use Await for long-running processes.
1801
- - Transcripts and @mentions for cross-reference.
1802
- - /worktree for risky changes.
1803
- - /best-of-n to compare approaches.
1804
- `
1805
- }
1806
- ];
2891
+ function readinessEvidence(report, checkId) {
2892
+ return report.pillars.flatMap((pillar2) => pillar2.checks).filter((check2) => check2.id === checkId).flatMap((check2) => check2.evidence);
1807
2893
  }
1808
- async function generateCursorArtifacts(profile) {
1809
- const rulesDir = path12.join(profile.rootDir, ".cursor", "rules");
1810
- const skillsDir = path12.join(profile.rootDir, ".cursor", "skills", "core");
1811
- const agentsDir = path12.join(profile.rootDir, ".cursor", "agents");
1812
- const commandsDir = path12.join(profile.rootDir, ".cursor", "commands");
1813
- await Promise.all([
1814
- ensureDir(rulesDir),
1815
- ensureDir(skillsDir),
1816
- ensureDir(agentsDir),
1817
- ensureDir(commandsDir)
1818
- ]);
1819
- const rules = cursorRulesByPlan(profile);
1820
- await Promise.all(
1821
- rules.map((rule) => writeFile5(path12.join(rulesDir, rule.filename), rule.content, "utf8"))
1822
- );
1823
- const includeAgents = profile.ide.plan !== "cursor-free";
1824
- if (includeAgents) {
1825
- await writeFile5(
1826
- path12.join(agentsDir, "security-reviewer.md"),
1827
- "# Security Reviewer\n\nFocus on auth, PII, secrets, injection, and logging.\n",
1828
- "utf8"
1829
- );
1830
- }
1831
- const ci = ciLabel(profile);
1832
- const provider = gitProviderLabel(profile);
1833
- const statusLines = [
1834
- "# /agent-kit-status",
1835
- "",
1836
- "Show current profile and active components.",
1837
- "",
1838
- "## DevOps Flow",
1839
- `- Git: ${provider} (${profile.git.workflow})`
1840
- ];
1841
- if (ci) statusLines.push(`- CI/CD: ${ci}`);
1842
- const pm = pmToolsList(profile);
1843
- if (pm) statusLines.push(`- Project management: ${pm}`);
1844
- statusLines.push("");
1845
- await writeFile5(
1846
- path12.join(commandsDir, "agent-kit-status.md"),
1847
- `${statusLines.join("\n")}
1848
- `,
1849
- "utf8"
2894
+ function hasPurpose(profile, purposes) {
2895
+ return purposes.some(
2896
+ (purpose) => profile.purpose.value === purpose || profile.purpose.categories.includes(purpose)
1850
2897
  );
1851
2898
  }
1852
-
1853
- // src/generator/git-hooks.ts
1854
- import { chmod, writeFile as writeFile6 } from "fs/promises";
1855
- import path13 from "path";
1856
- async function generateGitHooks(profile) {
1857
- if (!profile.installHooks) return;
1858
- const hooksDir = path13.join(profile.rootDir, ".git", "hooks");
1859
- await ensureDir(hooksDir);
1860
- const preCommitPath = path13.join(hooksDir, "pre-commit");
1861
- const preCommit = `#!/usr/bin/env bash
1862
- set -euo pipefail
1863
-
1864
- if command -v rg >/dev/null 2>&1; then
1865
- rg -n --hidden --glob '!node_modules/**' '(AKIA|BEGIN PRIVATE KEY|xoxb-)' . && {
1866
- echo "Potential secret detected. Commit blocked."
1867
- exit 1
1868
- } || true
1869
- fi
1870
- `;
1871
- await writeFile6(preCommitPath, preCommit, "utf8");
1872
- await chmod(preCommitPath, 493);
2899
+ function candidate(kind, id, evidence, status) {
2900
+ const verified = uniqueEvidence(evidence);
2901
+ return verified.length > 0 ? { kind, id, status, evidence: verified } : null;
1873
2902
  }
1874
-
1875
- // src/generator/vscode.ts
1876
- import { writeFile as writeFile7 } from "fs/promises";
1877
- import path14 from "path";
1878
- async function generateVSCodeArtifacts(profile) {
1879
- const vscodeDir = path14.join(profile.rootDir, ".vscode");
1880
- const githubDir = path14.join(profile.rootDir, ".github");
1881
- await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
1882
- await writeFile7(
1883
- path14.join(vscodeDir, "settings.json"),
1884
- `${JSON.stringify(
1885
- {
1886
- "editor.formatOnSave": true,
1887
- "editor.codeActionsOnSave": {
1888
- "source.fixAll": "explicit"
1889
- },
1890
- "files.autoSave": "afterDelay"
1891
- },
1892
- null,
1893
- 2
1894
- )}
1895
- `,
1896
- "utf8"
1897
- );
1898
- const provider = gitProviderLabel(profile);
1899
- const prTerm = prTerminology(profile);
1900
- await writeFile7(
1901
- path14.join(githubDir, "copilot-instructions.md"),
1902
- `# Copilot Instructions
1903
-
1904
- - Keep code changes small and testable.
1905
- - Use Conventional Commits (feat:, fix:, docs:, etc.).
1906
- - Prefer security-safe defaults.
1907
- - Git platform: ${provider}. Always create a ${prTerm} for review.
1908
- `,
1909
- "utf8"
1910
- );
1911
- if (profile.ide.plan === "vscode-pro") {
1912
- await writeFile7(
1913
- path14.join(vscodeDir, "security-review.agent.md"),
1914
- "# Security Review Agent\n\nSpecialized mode for security review.\n",
1915
- "utf8"
2903
+ function componentAvailable(index, item) {
2904
+ if (item.kind === "skill") return allSkills(index).some((entry) => entry.id === item.id);
2905
+ if (item.kind === "pack") return allPacks(index).some((entry) => entry.id === item.id);
2906
+ if (item.kind === "agent" || item.kind === "command") {
2907
+ return (index.artifacts ?? []).some(
2908
+ (entry) => entry.kind === item.kind && entry.id === item.id
1916
2909
  );
1917
2910
  }
1918
- }
1919
-
1920
- // src/generator/windsurf.ts
1921
- import { writeFile as writeFile8 } from "fs/promises";
1922
- import path15 from "path";
1923
- async function generateWindsurfArtifacts(profile) {
1924
- const provider = gitProviderLabel(profile);
1925
- const prTerm = prTerminology(profile);
1926
- const content = `# Windsurf Rules
1927
- - Keep instructions concise and objective.
1928
- - Prefer small diffs and explicit validation.
1929
- - Apply security review before merge.
1930
- - Use Conventional Commits (feat:, fix:, docs:, etc.).
1931
- - Git: ${provider}. Create a ${prTerm} for every change.
1932
- `;
1933
- await writeFile8(path15.join(profile.rootDir, ".windsurfrules"), content, "utf8");
1934
- }
1935
-
1936
- // src/generator/index.ts
1937
- async function generateFromProfile(profile) {
1938
- await generateAgentsMd(profile);
1939
- await generateGitHooks(profile);
1940
- if (profile.ide.ide === "cursor" || profile.ide.plan.startsWith("cursor")) {
1941
- await generateCursorArtifacts(profile);
1942
- }
1943
- if (profile.ide.ide === "vscode" || profile.ide.plan.startsWith("vscode")) {
1944
- await generateVSCodeArtifacts(profile);
1945
- }
1946
- if (profile.ide.ide === "windsurf" || profile.ide.plan === "windsurf") {
1947
- await generateWindsurfArtifacts(profile);
1948
- }
1949
- if (profile.ide.plan === "default") {
1950
- await Promise.all([
1951
- generateCursorArtifacts(profile),
1952
- generateVSCodeArtifacts(profile),
1953
- generateWindsurfArtifacts(profile)
1954
- ]);
1955
- }
1956
- const pluginDir = path16.join(profile.rootDir, ".cursor-plugin");
1957
- await ensureDir(pluginDir);
1958
- await writeFile9(
1959
- path16.join(pluginDir, "plugin.json"),
1960
- `${JSON.stringify(
1961
- {
1962
- name: "agent-kit",
1963
- displayName: "Agent Kit",
1964
- author: "agent-kit-startup",
1965
- description: "Bootstrap de ambiente dev com IA (Cursor, VS Code, Windsurf)",
1966
- keywords: ["agents", "context", "automation", "multi-ide"],
1967
- license: "MIT",
1968
- version: "3.0.0"
1969
- },
1970
- null,
1971
- 2
1972
- )}
1973
- `,
1974
- "utf8"
1975
- );
1976
- }
1977
-
1978
- // src/scanner/scan.ts
1979
- import path22 from "path";
1980
-
1981
- // src/scanner/detect-git.ts
1982
- import { execFile as execFile2 } from "child_process";
1983
- import path17 from "path";
1984
- import { promisify as promisify2 } from "util";
1985
- var exec = promisify2(execFile2);
1986
- function detectProvider(remoteUrl) {
1987
- if (!remoteUrl) return void 0;
1988
- if (remoteUrl.includes("github")) return "github";
1989
- if (remoteUrl.includes("gitlab")) return "gitlab";
1990
- if (remoteUrl.includes("bitbucket")) return "bitbucket";
1991
- if (remoteUrl.includes("dev.azure.com") || remoteUrl.includes("visualstudio.com"))
1992
- return "azure-devops";
1993
- if (remoteUrl.includes("gitea") || remoteUrl.includes("codeberg")) return "gitea";
1994
- return "other";
1995
- }
1996
- function inferWorkflow(currentBranch) {
1997
- if (!currentBranch) return "unknown";
1998
- if (currentBranch === "main" || currentBranch === "master") return "feature-pr";
1999
- if (currentBranch.includes("develop") || currentBranch.includes("release")) return "gitflow";
2000
- if (currentBranch.includes("staging") || currentBranch.includes("homolog")) return "homolog-prod";
2001
- return "feature-pr";
2002
- }
2003
- async function runGit(args, rootDir) {
2004
- try {
2005
- const { stdout } = await exec("git", args, { cwd: rootDir });
2006
- return stdout.trim();
2007
- } catch {
2008
- return void 0;
2009
- }
2010
- }
2011
- async function detectGit(rootDir) {
2012
- const hasGit = await fileExists(path17.join(rootDir, ".git"));
2013
- if (!hasGit) return { workflow: "unknown" };
2014
- const remoteUrl = await runGit(["remote", "get-url", "origin"], rootDir);
2015
- const currentBranch = await runGit(["branch", "--show-current"], rootDir);
2016
- return {
2017
- provider: detectProvider(remoteUrl),
2018
- remoteUrl,
2019
- currentBranch,
2020
- workflow: inferWorkflow(currentBranch)
2021
- };
2022
- }
2023
-
2024
- // src/scanner/detect-ide.ts
2025
- import path18 from "path";
2026
- async function detectIde(rootDir) {
2027
- const hasCursor = await fileExists(path18.join(rootDir, ".cursor"));
2028
- const hasVSCode = await fileExists(path18.join(rootDir, ".vscode"));
2029
- const hasWindsurf = await fileExists(path18.join(rootDir, ".windsurfrules"));
2030
- if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
2031
- if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
2032
- if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
2033
- return { ide: "unknown", plan: "default" };
2034
- }
2035
-
2036
- // src/scanner/detect-infra.ts
2037
- import path19 from "path";
2038
- async function detectInfra(rootDir) {
2039
- const docker = await fileExists(path19.join(rootDir, "Dockerfile")) || await fileExists(path19.join(rootDir, "docker-compose.yml")) || await fileExists(path19.join(rootDir, "docker-compose.yaml"));
2040
- const kubernetes = await fileExists(path19.join(rootDir, "k8s")) || await fileExists(path19.join(rootDir, "kubernetes"));
2041
- let ci = "none";
2042
- for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
2043
- if (await fileExists(path19.join(rootDir, filePath))) {
2044
- ci = platform;
2045
- break;
2046
- }
2047
- }
2048
- return { docker, kubernetes, ci };
2049
- }
2050
-
2051
- // src/scanner/detect-services.ts
2052
- import { readFile as readFile7 } from "fs/promises";
2053
- import path20 from "path";
2054
- async function detectProjectManagement(rootDir) {
2055
- const tools = [];
2056
- const mcpConfigPaths = [
2057
- path20.join(rootDir, ".cursor", "mcp.json"),
2058
- path20.join(rootDir, "mcp.json")
2911
+ return true;
2912
+ }
2913
+ function buildPersonalizationPlan(profile, report, registry) {
2914
+ const items = [];
2915
+ const purpose = purposeEvidence(profile);
2916
+ const packageEvidence = profile.stack.packageManagerEvidence ?? [];
2917
+ const n8nEvidence = [
2918
+ ...contextEvidence(profile, /(^|[/.-])n8n([/.-]|$)/i),
2919
+ ...purpose.filter((item) => /n8n/i.test(item.value))
2059
2920
  ];
2060
- for (const configPath of mcpConfigPaths) {
2061
- if (!await fileExists(configPath)) continue;
2062
- try {
2063
- const raw = await readFile7(configPath, "utf8");
2064
- const lower = raw.toLowerCase();
2065
- if (lower.includes("clickup")) tools.push("clickup");
2066
- if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
2067
- if (lower.includes("linear")) tools.push("linear");
2068
- if (lower.includes("asana")) tools.push("asana");
2069
- if (lower.includes("youtrack")) tools.push("youtrack");
2070
- if (lower.includes("shortcut")) tools.push("shortcut");
2071
- } catch {
2072
- }
2073
- }
2074
- if (await fileExists(path20.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
2075
- tools.push("github-issues");
2921
+ const sqlEvidence = [
2922
+ ...contextEvidence(profile, /(^|[/.-])(sql|schema|migration)([/.-]|$)/i),
2923
+ ...purpose.filter((item) => /\bsql\b/i.test(item.value))
2924
+ ];
2925
+ const promptEvidence = contextEvidence(profile, /(^|[/.-])prompts?([/.-]|$)/i);
2926
+ const ciEvidence = profile.infra.ciFiles.map((value) => ({ source: "file", value }));
2927
+ const infraEvidence = [
2928
+ ...ciEvidence,
2929
+ ...profile.infra.infrastructureFiles.map((value) => ({ source: "file", value })),
2930
+ ...profile.infra.deploymentFiles.map((value) => ({ source: "file", value }))
2931
+ ];
2932
+ if (hasPurpose(profile, ["documentation", "knowledge"])) {
2933
+ items.push(candidate("skill", "docs-repo", purpose, "applied"));
2076
2934
  }
2077
- if (await fileExists(path20.join(rootDir, ".github", "projects"))) {
2078
- tools.push("github-projects");
2935
+ if (profile.stack.language.toLowerCase() === "node" && packageEvidence.length > 0) {
2936
+ items.push(candidate("skill", "cursor-skills-node", packageEvidence, "applied"));
2079
2937
  }
2080
- return [...new Set(tools)];
2081
- }
2082
- async function detectServices(rootDir) {
2083
- const hasPrisma = await fileExists(path20.join(rootDir, "prisma/schema.prisma"));
2084
- const hasSequelize = await fileExists(path20.join(rootDir, "sequelize"));
2085
- const hasDrizzle = await fileExists(path20.join(rootDir, "drizzle.config.ts"));
2086
- const hasKnex = await fileExists(path20.join(rootDir, "knexfile.ts"));
2087
- const hasTypeorm = await fileExists(path20.join(rootDir, "ormconfig.json"));
2088
- const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
2089
- const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
2090
- const projectManagement = await detectProjectManagement(rootDir);
2091
- return {
2092
- database,
2093
- orm,
2094
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2095
- };
2096
- }
2097
-
2098
- // src/scanner/detect-stack.ts
2099
- import path21 from "path";
2100
- var PROJECT_MARKERS = [
2101
- "package.json",
2102
- "requirements.txt",
2103
- "pyproject.toml",
2104
- "go.mod",
2105
- "Gemfile",
2106
- "pom.xml",
2107
- "build.gradle",
2108
- "composer.json",
2109
- "Cargo.toml"
2110
- ];
2111
- async function detectStack(rootDir) {
2112
- const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path21.join(rootDir, item))))).some(Boolean);
2113
- const hasPackageJson = await fileExists(path21.join(rootDir, "package.json"));
2114
- if (hasPackageJson) {
2115
- const hasNextConfig = await fileExists(path21.join(rootDir, "next.config.js")) || await fileExists(path21.join(rootDir, "next.config.mjs")) || await fileExists(path21.join(rootDir, "next.config.ts"));
2116
- const hasNestConfig = await fileExists(path21.join(rootDir, "nest-cli.json"));
2117
- return {
2118
- language: "node",
2119
- framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
2120
- packageManager: "pnpm",
2121
- hasProjectFiles: hasAnyProjectMarker
2122
- };
2938
+ if (n8nEvidence.length > 0) {
2939
+ items.push(candidate("skill", "n8n-workflows", n8nEvidence, "applied"));
2940
+ } else if (hasPurpose(profile, ["automation"])) {
2941
+ items.push(candidate("skill", "n8n-workflows", purpose, "recommended-confirmation"));
2123
2942
  }
2124
- if (await fileExists(path21.join(rootDir, "pyproject.toml"))) {
2125
- return { language: "python", framework: "python", hasProjectFiles: hasAnyProjectMarker };
2943
+ if (sqlEvidence.length > 0) {
2944
+ items.push(candidate("skill", "sql-postgres", sqlEvidence, "applied"));
2945
+ } else if (profile.services.database?.toLowerCase().includes("postgres")) {
2946
+ items.push(
2947
+ candidate(
2948
+ "skill",
2949
+ "sql-postgres",
2950
+ [{ source: "configuration", value: `database:${profile.services.database}` }],
2951
+ "recommended-confirmation"
2952
+ )
2953
+ );
2126
2954
  }
2127
- if (await fileExists(path21.join(rootDir, "go.mod"))) {
2128
- return { language: "go", framework: "go", hasProjectFiles: hasAnyProjectMarker };
2955
+ if (promptEvidence.length > 0) {
2956
+ items.push(candidate("skill", "prompts-markdown", promptEvidence, "applied"));
2129
2957
  }
2130
- if (await fileExists(path21.join(rootDir, "Cargo.toml"))) {
2131
- return { language: "rust", framework: "rust", hasProjectFiles: hasAnyProjectMarker };
2958
+ if (infraEvidence.length > 0) {
2959
+ items.push(candidate("pack", "devops", infraEvidence, "applied"));
2132
2960
  }
2133
- if (await fileExists(path21.join(rootDir, "composer.json"))) {
2134
- return { language: "php", framework: "php", hasProjectFiles: hasAnyProjectMarker };
2961
+ if (profile.stack.testCommands.length > 0 && packageEvidence.length > 0) {
2962
+ items.push(candidate("pack", "quality", packageEvidence, "applied"));
2963
+ items.push(candidate("agent", "test-suites", packageEvidence, "applied"));
2135
2964
  }
2136
- return { language: "unknown", hasProjectFiles: hasAnyProjectMarker };
2137
- }
2138
-
2139
- // src/scanner/scan.ts
2140
- var GREENFIELD_SAFE_FILES = /* @__PURE__ */ new Set([
2141
- ".git",
2142
- ".gitignore",
2143
- "LICENSE",
2144
- "README.md",
2145
- ".cursor",
2146
- ".vscode"
2147
- ]);
2148
- function isGreenfieldByEntries(entries) {
2149
- const meaningful = entries.filter((entry) => !GREENFIELD_SAFE_FILES.has(entry));
2150
- return meaningful.length === 0;
2151
- }
2152
- async function runScanner(rootDir) {
2153
- const normalizedRoot = path22.resolve(rootDir);
2154
- const entries = await listDirectory(normalizedRoot);
2155
- const stack = await detectStack(normalizedRoot);
2156
- const isGreenfield = isGreenfieldByEntries(entries) || !stack.hasProjectFiles;
2157
- return {
2158
- rootDir: normalizedRoot,
2159
- isGreenfield,
2160
- stack,
2161
- git: await detectGit(normalizedRoot),
2162
- ide: await detectIde(normalizedRoot),
2163
- infra: await detectInfra(normalizedRoot),
2164
- services: await detectServices(normalizedRoot)
2165
- };
2166
- }
2167
-
2168
- // src/utils/prompts.ts
2169
- import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
2170
- var WORKSPACE_SKIN_MODE_DEFAULTS = {
2171
- default: "autopilot",
2172
- modes: {
2173
- "continue-plan": "autopilot",
2174
- "run-plan": "night-shift",
2175
- "cli-run-plan": "ghost-runner"
2965
+ const pmTools = (profile.services.projectManagement ?? []).filter((tool) => tool !== "none");
2966
+ if (pmTools.length > 0) {
2967
+ items.push(
2968
+ candidate(
2969
+ "pack",
2970
+ "project-management",
2971
+ pmTools.map((tool) => ({ source: "configuration", value: `projectManagement:${tool}` })),
2972
+ "recommended-confirmation"
2973
+ )
2974
+ );
2176
2975
  }
2177
- };
2178
- function workspaceSkinConfigFromChoice(choice) {
2179
- if (choice.kind === "skip") return null;
2180
- if (choice.kind === "mode-defaults")
2181
- return { ...WORKSPACE_SKIN_MODE_DEFAULTS, modes: { ...WORKSPACE_SKIN_MODE_DEFAULTS.modes } };
2182
- return {
2183
- default: choice.id,
2184
- modes: {
2185
- "continue-plan": choice.id,
2186
- "run-plan": choice.id,
2187
- "cli-run-plan": choice.id
2188
- }
2189
- };
2190
- }
2191
- function ensureNotCancelled(value) {
2192
- if (isCancel(value)) {
2193
- cancel("Operation cancelled.");
2194
- process.exit(0);
2195
- }
2196
- return value;
2197
- }
2198
- async function askIdeAndPlan(current) {
2199
- const ide = ensureNotCancelled(
2200
- await select({
2201
- message: "Which is your main IDE?",
2202
- initialValue: current.ide === "unknown" ? void 0 : current.ide,
2203
- options: [
2204
- { label: "Cursor", value: "cursor" },
2205
- { label: "VS Code", value: "vscode" },
2206
- { label: "Windsurf", value: "windsurf" },
2207
- { label: "Other", value: "other" }
2208
- ]
2209
- })
2210
- );
2211
- const plan = ensureNotCancelled(
2212
- await select({
2213
- message: "What's your IDE plan?",
2214
- initialValue: current.plan === "default" ? void 0 : current.plan,
2215
- options: [
2216
- { label: "Cursor Free", value: "cursor-free" },
2217
- { label: "Cursor Pro / Business", value: "cursor-pro" },
2218
- { label: "VS Code + Copilot Free", value: "vscode-free" },
2219
- { label: "VS Code + Copilot Pro / Business", value: "vscode-pro" },
2220
- { label: "Windsurf", value: "windsurf" },
2221
- { label: "Don't know / default", value: "default" }
2222
- ]
2223
- })
2224
- );
2225
- return { ide, plan };
2226
- }
2227
- async function askGitWorkflow(current) {
2228
- return ensureNotCancelled(
2229
- await select({
2230
- message: "What's your Git workflow?",
2231
- initialValue: current === "unknown" ? void 0 : current,
2232
- options: [
2233
- { label: "trunk-based (direct to main)", value: "trunk-based" },
2234
- { label: "feature branch -> PR/MR -> main", value: "feature-pr" },
2235
- { label: "gitflow (develop/release/main)", value: "gitflow" },
2236
- { label: "staging -> production (staging -> main)", value: "homolog-prod" }
2237
- ]
2238
- })
2976
+ const safetyEvidence = readinessEvidence(report, "safety.secrets").filter(
2977
+ (item) => item.value.startsWith("tracked:")
2239
2978
  );
2240
- }
2241
- async function askProjectManagement(existing = []) {
2242
- return ensureNotCancelled(
2243
- await multiselect({
2244
- message: "Do you use any project management system?",
2245
- initialValues: existing,
2246
- options: [
2247
- { label: "GitHub Issues / Projects", value: "github-issues" },
2248
- { label: "Jira", value: "jira" },
2249
- { label: "Linear", value: "linear" },
2250
- { label: "ClickUp", value: "clickup" },
2251
- { label: "Azure Boards", value: "azure-boards" },
2252
- { label: "Asana", value: "asana" },
2253
- { label: "Trello", value: "trello" },
2254
- { label: "Shortcut", value: "shortcut" },
2255
- { label: "Notion", value: "notion" },
2256
- { label: "YouTrack", value: "youtrack" }
2257
- ],
2258
- required: false
2259
- })
2979
+ if (safetyEvidence.length > 0) {
2980
+ items.push(candidate("pack", "cybersec", safetyEvidence, "recommended-confirmation"));
2981
+ }
2982
+ items.push(
2983
+ candidate(
2984
+ "command",
2985
+ "start-project",
2986
+ readinessEvidence(report, "agent-kit.context"),
2987
+ "applied"
2988
+ )
2260
2989
  );
2990
+ const availableItems = items.filter((item) => item !== null);
2991
+ return availableItems.map(
2992
+ (item) => componentAvailable(registry, item) ? item : { ...item, status: "unavailable" }
2993
+ ).sort((left, right) => `${left.kind}:${left.id}`.localeCompare(`${right.kind}:${right.id}`));
2994
+ }
2995
+ function renderProjectContext(profile) {
2996
+ const sections = ["# Project Context", "", "Verified repository facts:"];
2997
+ const purpose = purposeEvidence(profile);
2998
+ if (purpose.length > 0 && profile.purpose.value !== "unknown") {
2999
+ sections.push(`- Purpose: ${profile.purpose.value}.`);
3000
+ }
3001
+ const packageEvidence = profile.stack.packageManagerEvidence ?? [];
3002
+ if (packageEvidence.length > 0) {
3003
+ sections.push(`- Runtime family: ${profile.stack.language}.`);
3004
+ if (profile.stack.packageManager) {
3005
+ sections.push(`- Package manager: ${profile.stack.packageManager}.`);
3006
+ }
3007
+ }
3008
+ if (profile.git.provider && (profile.git.providerEvidence?.length ?? 0) > 0) {
3009
+ sections.push(`- Git provider: ${profile.git.provider}.`);
3010
+ }
3011
+ if (profile.infra.ci !== "none" && profile.infra.ciFiles.length > 0) {
3012
+ sections.push(`- CI: ${profile.infra.ci}.`);
3013
+ }
3014
+ if (profile.context.sources.length > 0) {
3015
+ sections.push("", "## Sources", ...profile.context.sources.map((item) => `- ${item.value}`));
3016
+ }
3017
+ return `${sections.join("\n")}
3018
+ `;
2261
3019
  }
2262
- async function askWorkspaceSkin() {
2263
- const value = ensureNotCancelled(
2264
- await select({
2265
- message: "Workspace skin for chat/CLI chrome? (optional)",
2266
- initialValue: "mode-defaults",
2267
- options: [
2268
- {
2269
- label: "Keep mode defaults (Autopilot/Night Shift/Ghost Runner per mode)",
2270
- value: "mode-defaults"
2271
- },
2272
- { label: "Autopilot", value: "autopilot" },
2273
- { label: "Night Shift", value: "night-shift" },
2274
- { label: "Ghost Runner", value: "ghost-runner" },
2275
- { label: "Skip for now", value: "skip" }
2276
- ]
2277
- })
2278
- );
2279
- if (value === "skip") return { kind: "skip" };
2280
- if (value === "mode-defaults") return { kind: "mode-defaults" };
2281
- return { kind: "skin", id: value };
2282
- }
2283
- async function runExistingProjectWizard(scan) {
2284
- const ide = await askIdeAndPlan(scan.ide);
2285
- const workflow = await askGitWorkflow(scan.git.workflow);
2286
- const projectManagement = await askProjectManagement(scan.services.projectManagement ?? []);
2287
- const installHooks = ensureNotCancelled(
2288
- await confirm({
2289
- message: "Install git hooks? (pre-commit: secrets + lint)",
2290
- initialValue: true
2291
- })
2292
- );
2293
- const workspaceSkinChoice = await askWorkspaceSkin();
3020
+ async function createOwnedFile(rootDir, relativePath, content, evidence) {
3021
+ const target = path21.join(rootDir, relativePath);
3022
+ if (await fileExists(target)) {
3023
+ return {
3024
+ kind: "file",
3025
+ id: relativePath,
3026
+ path: relativePath,
3027
+ status: "skipped-customized",
3028
+ evidence
3029
+ };
3030
+ }
3031
+ await ensureDir(path21.dirname(target));
3032
+ await writeFile5(target, content, "utf8");
2294
3033
  return {
2295
- rootDir: scan.rootDir,
2296
- stack: scan.stack,
2297
- git: { ...scan.git, workflow },
2298
- ide,
2299
- infra: scan.infra,
2300
- services: {
2301
- ...scan.services,
2302
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2303
- },
2304
- installHooks,
2305
- selectedCoreComponents: [
2306
- "git-workflow",
2307
- "security-review",
2308
- "clean-code",
2309
- "docs-repo",
2310
- "ide-guide"
2311
- ],
2312
- workspaceSkinChoice
3034
+ kind: "file",
3035
+ id: relativePath,
3036
+ path: relativePath,
3037
+ status: "applied",
3038
+ evidence
2313
3039
  };
2314
3040
  }
2315
- async function runGreenfieldWizard(scan) {
2316
- const language = ensureNotCancelled(
2317
- await select({
2318
- message: "What will be the main stack?",
2319
- options: [
2320
- { label: "Node.js (JavaScript/TypeScript)", value: "node" },
2321
- { label: "Python", value: "python" },
2322
- { label: "Go", value: "go" },
2323
- { label: "Ruby", value: "ruby" },
2324
- { label: "Java / Kotlin", value: "java" },
2325
- { label: "PHP", value: "php" },
2326
- { label: "Rust", value: "rust" },
2327
- { label: "C# / .NET", value: "dotnet" },
2328
- { label: "Other", value: "other" }
2329
- ]
2330
- })
2331
- );
2332
- const framework = ensureNotCancelled(
2333
- await text({
2334
- message: "Main framework? (ex: nextjs, nestjs, django, rails, spring, none)",
2335
- placeholder: "none",
2336
- initialValue: "none",
2337
- validate(value) {
2338
- return value.trim().length === 0 ? "Enter a framework or 'none'" : void 0;
2339
- }
2340
- })
2341
- );
2342
- const database = ensureNotCancelled(
2343
- await select({
2344
- message: "Will you use a database?",
2345
- options: [
2346
- { label: "PostgreSQL", value: "postgresql" },
2347
- { label: "MySQL", value: "mysql" },
2348
- { label: "MongoDB", value: "mongodb" },
2349
- { label: "SQLite", value: "sqlite" },
2350
- { label: "SQL Server", value: "sqlserver" },
2351
- { label: "None for now", value: "none" }
2352
- ]
2353
- })
2354
- );
2355
- const orm = ensureNotCancelled(
2356
- await text({
2357
- message: "ORM / query builder?",
2358
- placeholder: "prisma, drizzle, sequelize, typeorm, none",
2359
- initialValue: "none"
2360
- })
2361
- );
2362
- const ide = await askIdeAndPlan(scan.ide);
2363
- const workflow = await askGitWorkflow(scan.git.workflow);
2364
- const projectManagement = await askProjectManagement();
2365
- const installHooks = ensureNotCancelled(
2366
- await confirm({
2367
- message: "Install git hooks? (pre-commit: secrets + lint)",
2368
- initialValue: true
2369
- })
2370
- );
2371
- const workspaceSkinChoice = await askWorkspaceSkin();
2372
- return {
2373
- rootDir: scan.rootDir,
2374
- stack: {
2375
- language,
2376
- framework: framework === "none" ? void 0 : framework,
2377
- hasProjectFiles: false
2378
- },
2379
- git: {
2380
- ...scan.git,
2381
- workflow
2382
- },
2383
- ide,
2384
- infra: scan.infra,
2385
- services: {
2386
- database: database === "none" ? void 0 : database,
2387
- orm: orm === "none" ? void 0 : orm,
2388
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2389
- },
2390
- installHooks,
2391
- selectedCoreComponents: [
2392
- "git-workflow",
2393
- "security-review",
2394
- "clean-code",
2395
- "docs-repo",
2396
- "ide-guide"
2397
- ],
2398
- workspaceSkinChoice
2399
- };
3041
+ async function packTargets(registryRoot, packId) {
3042
+ const manifest = await loadPackManifest(registryRoot, packId);
3043
+ return manifest.members.map((member) => packMemberTargets(member).targetRel);
2400
3044
  }
2401
-
2402
- // src/commands/init.ts
2403
- async function mergeWorkspaceSkinConfig(rootDir, workspaceSkin) {
2404
- const contextDir = path23.join(rootDir, ".cursor", "context");
2405
- const configPath = path23.join(contextDir, "config.json");
2406
- await ensureDir(contextDir);
2407
- const existing = await readJson(configPath) ?? {};
2408
- await writeJson(configPath, { ...existing, workspaceSkin });
2409
- return configPath;
2410
- }
2411
- var initCommand = defineCommand5({
2412
- meta: {
2413
- name: "init",
2414
- description: "Run scanner + wizard + generation setup."
2415
- },
2416
- args: {
2417
- cwd: {
2418
- type: "string",
2419
- description: "Project root directory",
2420
- default: process.cwd()
3045
+ async function existingTargets(projectRoot, targets) {
3046
+ const checks = await Promise.all(
3047
+ targets.map(
3048
+ async (target) => await fileExists(path21.join(projectRoot, target)) ? target : null
3049
+ )
3050
+ );
3051
+ return checks.filter((target) => target !== null);
3052
+ }
3053
+ async function applyPersonalization(input) {
3054
+ const planned = buildPersonalizationPlan(input.profile, input.report, input.registry);
3055
+ const componentResults = [];
3056
+ const protectedPaths = new Set(input.manifest.protected ?? []);
3057
+ const packs = new Set(input.manifest.packs ?? []);
3058
+ const skills = new Set(input.manifest.skills ?? []);
3059
+ for (const item of planned) {
3060
+ if (item.status !== "applied") {
3061
+ componentResults.push(item);
3062
+ continue;
2421
3063
  }
2422
- },
2423
- async run({ args }) {
2424
- intro("agent-kit v3.0.0");
2425
- const scan = await runScanner(args.cwd);
2426
- logger.info(`Root: ${scan.rootDir}`);
2427
- logger.info(
2428
- scan.isGreenfield ? "Empty repo detected (greenfield)." : "Existing project detected."
2429
- );
2430
- const profile = scan.isGreenfield ? await runGreenfieldWizard(scan) : await runExistingProjectWizard(scan);
2431
- const { workspaceSkinChoice, ...profileToSave } = profile;
2432
- const configPath = path23.join(scan.rootDir, ".cursor", "agent-kit.config.json");
2433
- await writeJson(configPath, profileToSave);
2434
- logger.success(`Profile saved in ${configPath}`);
2435
- const skinConfig = workspaceSkinChoice !== void 0 ? workspaceSkinConfigFromChoice(workspaceSkinChoice) : null;
2436
- if (skinConfig) {
2437
- const contextConfigPath = await mergeWorkspaceSkinConfig(scan.rootDir, skinConfig);
2438
- logger.success(`Workspace skin saved in ${contextConfigPath}`);
2439
- }
2440
- await generateFromProfile(profile);
2441
- try {
2442
- const registry = await resolveRegistryRoot({ cwd: scan.rootDir });
2443
- const stats = await installSkillsByIds(
2444
- registry.root,
2445
- scan.rootDir,
2446
- profile.selectedCoreComponents
3064
+ if (item.kind === "skill") {
3065
+ const skill = allSkills(input.registry).find((entry) => entry.id === item.id);
3066
+ if (!skill) {
3067
+ componentResults.push({ ...item, status: "unavailable" });
3068
+ continue;
3069
+ }
3070
+ const target = path21.posix.join(
3071
+ ".cursor",
3072
+ "skills",
3073
+ skill.path.includes("/core/") ? "core" : "community",
3074
+ skill.id,
3075
+ "SKILL.md"
2447
3076
  );
2448
- if (stats.written.length > 0) {
2449
- logger.success(`Skills installed: ${stats.written.join(", ")}`);
2450
- } else if (stats.missing.length > 0) {
2451
- logger.warn(`Skills not found in registry: ${stats.missing.join(", ")}`);
2452
- } else {
2453
- logger.info("No new skills to copy (already installed or empty selection).");
3077
+ if (await fileExists(path21.join(input.rootDir, target))) {
3078
+ componentResults.push({ ...item, status: "skipped-customized", path: target });
3079
+ protectedPaths.add(target);
3080
+ continue;
2454
3081
  }
2455
- } catch {
2456
- logger.info(
2457
- "Registry unavailable in this directory: use agent-kit install / agent-kit add <id> with --registry or remote cache."
3082
+ await installSkill(input.registryRoot, input.rootDir, skill);
3083
+ skills.add(item.id);
3084
+ protectedPaths.add(target);
3085
+ componentResults.push({ ...item, path: target });
3086
+ continue;
3087
+ }
3088
+ if (item.kind === "pack") {
3089
+ const targets = await packTargets(input.registryRoot, item.id);
3090
+ const customizedTargets = await existingTargets(input.rootDir, targets);
3091
+ await installPack(input.registryRoot, input.rootDir, item.id, {
3092
+ protectedGlobs: customizedTargets
3093
+ });
3094
+ for (const target of targets) protectedPaths.add(target);
3095
+ packs.add(item.id);
3096
+ componentResults.push(
3097
+ customizedTargets.length > 0 ? { ...item, status: "skipped-customized" } : item
2458
3098
  );
3099
+ continue;
2459
3100
  }
2460
- console.log("\nNext:");
2461
- console.log(` Open this folder in Cursor: ${scan.rootDir}`);
2462
- console.log(" Run /onboard in chat; then /start-project when you have a goal");
2463
- console.log(" Optional: agent-kit status");
2464
- outro("Setup completed.");
3101
+ componentResults.push(item);
2465
3102
  }
2466
- });
3103
+ const profileEvidence = uniqueEvidence([
3104
+ ...purposeEvidence(input.profile),
3105
+ ...input.profile.stack.packageManagerEvidence ?? [],
3106
+ ...input.profile.git.providerEvidence ?? [],
3107
+ ...input.profile.context.sources
3108
+ ]);
3109
+ const fileResults = await Promise.all([
3110
+ createOwnedFile(
3111
+ input.rootDir,
3112
+ CONTEXT_PATH,
3113
+ renderProjectContext(input.profile),
3114
+ profileEvidence
3115
+ ),
3116
+ createOwnedFile(
3117
+ input.rootDir,
3118
+ AGENTS_PATH,
3119
+ "# Repository Agent Guidance\n\nUse `.cursor/project-context.md` for verified repository facts. Preserve existing project-owned guidance and request confirmation before adding optional integrations.\n",
3120
+ profileEvidence
3121
+ )
3122
+ ]);
3123
+ protectedPaths.add(CONTEXT_PATH);
3124
+ protectedPaths.add(AGENTS_PATH);
3125
+ const result = {
3126
+ contractVersion: PERSONALIZATION_CONTRACT_VERSION,
3127
+ generatorVersion: input.generatorVersion,
3128
+ repositoryFingerprint: input.report.repositoryFingerprint,
3129
+ items: [...fileResults, ...componentResults],
3130
+ protectedPaths: [...protectedPaths].sort()
3131
+ };
3132
+ await writeJson(path21.join(input.rootDir, RESULT_PATH), result);
3133
+ return {
3134
+ result,
3135
+ manifest: {
3136
+ ...input.manifest,
3137
+ packs: [...packs].sort(),
3138
+ skills: [...skills].sort(),
3139
+ protected: result.protectedPaths,
3140
+ personalization: {
3141
+ contractVersion: PERSONALIZATION_CONTRACT_VERSION,
3142
+ generatorVersion: input.generatorVersion,
3143
+ origin: "repository-profile",
3144
+ resultPath: RESULT_PATH
3145
+ }
3146
+ }
3147
+ };
3148
+ }
3149
+ async function readRepositoryProfile(rootDir) {
3150
+ const target = path21.join(rootDir, ".cursor/agent-kit.config.json");
3151
+ if (!await fileExists(target)) return null;
3152
+ return JSON.parse(await readFile10(target, "utf8"));
3153
+ }
2467
3154
 
2468
- // src/commands/install.ts
2469
- import path24 from "path";
2470
- import { defineCommand as defineCommand6 } from "citty";
3155
+ // src/lifecycle/onboard-migration.ts
3156
+ import { createHash as createHash3 } from "crypto";
3157
+ import { readFile as readFile11, unlink } from "fs/promises";
3158
+ import path22 from "path";
3159
+ var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
3160
+ var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
3161
+ var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
3162
+ "b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
3163
+ ]);
3164
+ async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
3165
+ const legacyPath = path22.join(projectRoot, LEGACY_ONBOARD_PATH);
3166
+ if (!await fileExists(legacyPath)) return "absent";
3167
+ const namespacedPath = path22.join(projectRoot, NAMESPACED_ONBOARD_PATH);
3168
+ if (!await fileExists(namespacedPath)) return "preserved-customized";
3169
+ const content = await readFile11(legacyPath);
3170
+ const hash = createHash3("sha256").update(content).digest("hex");
3171
+ if (!managedHashes.has(hash)) return "preserved-customized";
3172
+ await unlink(legacyPath);
3173
+ return "removed-managed";
3174
+ }
2471
3175
 
2472
3176
  // src/lifecycle/sync.ts
2473
3177
  async function installL0(registryRoot, projectRoot, protectedGlobs) {
@@ -2482,6 +3186,12 @@ async function installL0(registryRoot, projectRoot, protectedGlobs) {
2482
3186
  );
2483
3187
  recordOutcome(stats, artifact.target, outcome);
2484
3188
  }
3189
+ const migration = await migrateLegacyOnboardCommand(projectRoot);
3190
+ if (migration === "removed-managed") {
3191
+ stats.removed.push(".cursor/commands/onboard.md");
3192
+ } else if (migration === "preserved-customized") {
3193
+ stats.collisions.push(".cursor/commands/onboard.md");
3194
+ }
2485
3195
  return stats;
2486
3196
  }
2487
3197
  async function syncFromManifest(registryRoot, projectRoot, manifest) {
@@ -2511,11 +3221,73 @@ function parsePackList(raw) {
2511
3221
  )
2512
3222
  ];
2513
3223
  }
2514
- function printInstallNextSteps(projectRoot) {
2515
- console.log("\nNext:");
2516
- console.log(` Open this folder in Cursor: ${projectRoot}`);
2517
- console.log(" Run /onboard in chat; then /start-project when you have a goal");
2518
- console.log(" Optional: agent-kit status");
3224
+ function printReadinessNarrative(result) {
3225
+ const { summary, pendingActions } = result.readiness;
3226
+ const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
3227
+ console.log("\nRepository readiness");
3228
+ console.log(
3229
+ ` ready: ${summary.ready}, choices: ${summary.needs_choice}, manual: ${summary.manual}, blocked: ${summary.blocked}`
3230
+ );
3231
+ console.log(` safe fixes applied: ${fixed}`);
3232
+ console.log(` pending actions: ${pendingActions.length}`);
3233
+ console.log(
3234
+ pendingActions.length > 0 ? "Next: run /agent-kit-onboard in Cursor to resolve the first pending action" : "Next: run /start-project in Cursor when you have a deliverable"
3235
+ );
3236
+ }
3237
+ async function performInstall(options) {
3238
+ const projectRoot = path23.resolve(options.cwd);
3239
+ const packs = parsePackList(options.pack);
3240
+ const existing = await loadAgentKitManifest(projectRoot);
3241
+ const registry = await resolveRegistryFromCli({
3242
+ cwd: projectRoot,
3243
+ registry: options.registry,
3244
+ url: options.url,
3245
+ ref: options.ref,
3246
+ refresh: options.refresh,
3247
+ manifest: existing
3248
+ });
3249
+ const draft = buildManifest({
3250
+ version: KIT_VERSION,
3251
+ profile: options.profile ?? existing?.profile ?? "default",
3252
+ packs: packs.length > 0 ? packs : existing?.packs,
3253
+ skills: existing?.skills,
3254
+ protected: existing?.protected,
3255
+ personalization: existing?.personalization,
3256
+ registryUrl: registry.url ?? existing?.registry?.url,
3257
+ registryRef: registry.ref ?? existing?.registry?.ref
3258
+ });
3259
+ const stats = (draft.packs?.length ?? 0) > 0 || (draft.skills?.length ?? 0) > 0 ? await syncFromManifest(registry.root, projectRoot, draft) : await installL0(registry.root, projectRoot, resolveProtectedGlobs(draft));
3260
+ const manifestPath = await saveManifest(projectRoot, draft);
3261
+ const readinessExecution = await executeSafeReadinessFixes(projectRoot, {
3262
+ generatorVersion: KIT_VERSION
3263
+ });
3264
+ let readiness = readinessExecution.after;
3265
+ const profile = await readRepositoryProfile(projectRoot);
3266
+ if (profile) {
3267
+ const registryIndex = await loadRegistry(registry.root);
3268
+ const personalization = await applyPersonalization({
3269
+ rootDir: projectRoot,
3270
+ registryRoot: registry.root,
3271
+ profile,
3272
+ report: readinessExecution.after,
3273
+ registry: registryIndex,
3274
+ manifest: draft,
3275
+ generatorVersion: KIT_VERSION
3276
+ });
3277
+ await saveManifest(projectRoot, personalization.manifest);
3278
+ readiness = createReadinessReport(await runScanner(projectRoot), {
3279
+ generatorVersion: KIT_VERSION
3280
+ });
3281
+ readiness.appliedSafeFixes = readinessExecution.after.appliedSafeFixes;
3282
+ }
3283
+ await writeReadinessSnapshot(projectRoot, readiness);
3284
+ return {
3285
+ projectRoot,
3286
+ manifestPath,
3287
+ stats,
3288
+ readiness,
3289
+ safeChanges: readinessExecution.changes
3290
+ };
2519
3291
  }
2520
3292
  var installCommand = defineCommand6({
2521
3293
  meta: {
@@ -2539,50 +3311,61 @@ var installCommand = defineCommand6({
2539
3311
  ...REGISTRY_CLI_ARGS
2540
3312
  },
2541
3313
  async run({ args }) {
2542
- const projectRoot = path24.resolve(args.cwd);
3314
+ const projectRoot = path23.resolve(args.cwd);
2543
3315
  logger.info(`Installing into: ${projectRoot}`);
2544
3316
  const packs = parsePackList(args.pack);
2545
3317
  for (const id of packs) {
2546
3318
  if (!DOMAIN_PACK_IDS.includes(id)) {
2547
- logger.warn(`Pack '${id}' is not in the known L1 list \u2014 will still try registry.`);
3319
+ logger.warn(`Pack '${id}' is not in the known L1 list; will still try registry.`);
2548
3320
  }
2549
3321
  }
2550
- const existing = await loadAgentKitManifest(projectRoot);
2551
- const registry = await resolveRegistryFromCli({
3322
+ const result = await performInstall({
2552
3323
  cwd: projectRoot,
3324
+ profile: args.profile,
3325
+ pack: args.pack,
2553
3326
  registry: args.registry,
2554
3327
  url: args.url,
2555
3328
  ref: args.ref,
2556
- refresh: args.refresh,
2557
- manifest: existing
2558
- });
2559
- logger.info(`Registry: ${registry.root} (${registry.source})`);
2560
- const draft = buildManifest({
2561
- version: KIT_VERSION,
2562
- profile: args.profile ?? existing?.profile ?? "default",
2563
- packs: packs.length > 0 ? packs : existing?.packs,
2564
- skills: existing?.skills,
2565
- protected: existing?.protected,
2566
- registryUrl: registry.url ?? existing?.registry?.url,
2567
- registryRef: registry.ref ?? existing?.registry?.ref
3329
+ refresh: args.refresh
2568
3330
  });
2569
- let stats;
2570
- if ((draft.packs?.length ?? 0) > 0 || (draft.skills?.length ?? 0) > 0) {
2571
- stats = await syncFromManifest(registry.root, projectRoot, draft);
2572
- } else {
2573
- const protectedGlobs = resolveProtectedGlobs(draft);
2574
- stats = await installL0(registry.root, projectRoot, protectedGlobs);
3331
+ logApplyStats(result.stats);
3332
+ logger.success(`Manifest written: ${result.manifestPath}`);
3333
+ logger.success("Readiness snapshot written: .cursor/context/readiness.json");
3334
+ printReadinessNarrative(result);
3335
+ }
3336
+ });
3337
+
3338
+ // src/commands/init.ts
3339
+ async function runInitCompatibility(cwd, installer = performInstall) {
3340
+ return installer({ cwd });
3341
+ }
3342
+ var initCommand = defineCommand7({
3343
+ meta: {
3344
+ name: "init",
3345
+ description: "Guided compatibility entry point for install and repository readiness."
3346
+ },
3347
+ args: {
3348
+ cwd: {
3349
+ type: "string",
3350
+ description: "Project root directory",
3351
+ default: process.cwd()
2575
3352
  }
2576
- const manifestPath = await saveManifest(projectRoot, draft);
2577
- logApplyStats(stats);
2578
- logger.success(`Manifest written: ${manifestPath}`);
2579
- printInstallNextSteps(projectRoot);
3353
+ },
3354
+ async run({ args }) {
3355
+ intro(`agent-kit v${KIT_VERSION}`);
3356
+ logger.info("init now uses the canonical install and readiness workflow.");
3357
+ const result = await runInitCompatibility(args.cwd);
3358
+ const pending = result.readiness.pendingActions.length;
3359
+ logger.success(`L0 and readiness prepared in ${result.projectRoot}`);
3360
+ outro(
3361
+ pending > 0 ? "Next: run /agent-kit-onboard in Cursor to resolve the first pending action." : "Next: run /start-project in Cursor when you have a deliverable."
3362
+ );
2580
3363
  }
2581
3364
  });
2582
3365
 
2583
3366
  // src/commands/run-plan.ts
2584
- import path29 from "path";
2585
- import { defineCommand as defineCommand7 } from "citty";
3367
+ import path28 from "path";
3368
+ import { defineCommand as defineCommand8 } from "citty";
2586
3369
 
2587
3370
  // src/plan-loop/backends.ts
2588
3371
  import { execFileSync, spawn as spawn2 } from "child_process";
@@ -2666,13 +3449,14 @@ function listBackendIds() {
2666
3449
  }
2667
3450
 
2668
3451
  // src/plan-loop/run-loop.ts
2669
- import { mkdir as mkdir4, readFile as readFile10, rm, unlink } from "fs/promises";
2670
- import path28 from "path";
3452
+ import { mkdir as mkdir4, readFile as readFile14, rm, unlink as unlink2 } from "fs/promises";
3453
+ import path27 from "path";
2671
3454
 
2672
3455
  // src/plan-loop/external-review.ts
2673
3456
  import { spawn as spawn3 } from "child_process";
2674
- import path25 from "path";
2675
- var SCRIPT_REL = path25.join("scripts", "plan-external-review.sh");
3457
+ import path24 from "path";
3458
+ var CANONICAL_REL = path24.join(".cursor", "scripts", "plan-external-review.sh");
3459
+ var FALLBACK_REL = path24.join("scripts", "plan-external-review.sh");
2676
3460
  function isPlanExhaustedReason(reason) {
2677
3461
  const r = reason.trim().toLowerCase();
2678
3462
  if (!r) return false;
@@ -2689,20 +3473,32 @@ function shouldArmExternalPlanReview(input) {
2689
3473
  if (input.stopReason && isPlanExhaustedReason(input.stopReason)) return true;
2690
3474
  return false;
2691
3475
  }
2692
- async function armExternalPlanReview(root, deps = {}) {
2693
- const spawnFn = deps.spawnFn ?? spawn3;
2694
- const existsFn = deps.existsFn ?? fileExists;
2695
- const log = deps.log ?? ((line) => console.log(line));
2696
- const scriptPath = path25.join(root, SCRIPT_REL);
2697
- if (!await existsFn(scriptPath)) {
3476
+ async function armExternalPlanReview(root, options = {}) {
3477
+ const spawnFn = options.spawnFn ?? spawn3;
3478
+ const existsFn = options.existsFn ?? fileExists;
3479
+ const log = options.log ?? ((line) => console.log(line));
3480
+ const force = options.force === true;
3481
+ const canonicalPath = path24.join(root, CANONICAL_REL);
3482
+ const fallbackPath = path24.join(root, FALLBACK_REL);
3483
+ let scriptPath = null;
3484
+ let scriptRel = CANONICAL_REL;
3485
+ if (await existsFn(canonicalPath)) {
3486
+ scriptPath = canonicalPath;
3487
+ scriptRel = CANONICAL_REL;
3488
+ } else if (await existsFn(fallbackPath)) {
3489
+ scriptPath = fallbackPath;
3490
+ scriptRel = FALLBACK_REL;
3491
+ }
3492
+ if (!scriptPath) {
2698
3493
  log(
2699
- `tip: ${SCRIPT_REL} missing. Manual: /plan-external-review (enable externalPlanReview in .cursor/context/config.json).`
3494
+ `tip: ${CANONICAL_REL} missing (optional wrapper: ${FALLBACK_REL}). Manual: /plan-external-review (enable externalPlanReview in .cursor/context/config.json).`
2700
3495
  );
2701
3496
  return { invoked: false, exitCode: null, output: "" };
2702
3497
  }
2703
3498
  log("Plan exhausted: arming optional external plan review (opt-in handled by script)...");
3499
+ const args = force ? [scriptPath, "--force"] : [scriptPath];
2704
3500
  return new Promise((resolve) => {
2705
- const child = spawnFn("bash", [scriptPath], {
3501
+ const child = spawnFn("bash", args, {
2706
3502
  cwd: root,
2707
3503
  env: process.env
2708
3504
  });
@@ -2720,14 +3516,14 @@ async function armExternalPlanReview(root, deps = {}) {
2720
3516
  });
2721
3517
  child.on("error", (err) => {
2722
3518
  log(
2723
- `tip: external plan review launcher failed (${err.message}). Manual: ${SCRIPT_REL} or /plan-external-review`
3519
+ `tip: external plan review launcher failed (${err.message}). Manual: ${scriptRel} or /plan-external-review`
2724
3520
  );
2725
3521
  resolve({ invoked: true, exitCode: null, output });
2726
3522
  });
2727
3523
  child.on("close", (code) => {
2728
3524
  if (code !== 0 && code !== null) {
2729
3525
  log(
2730
- `tip: external plan review exited ${code} (ignored; does not fail the loop). Manual: ${SCRIPT_REL} or /plan-external-review`
3526
+ `tip: external plan review exited ${code} (ignored; does not fail the loop). Manual: ${scriptRel} or /plan-external-review`
2731
3527
  );
2732
3528
  }
2733
3529
  resolve({ invoked: true, exitCode: code, output });
@@ -2736,8 +3532,8 @@ async function armExternalPlanReview(root, deps = {}) {
2736
3532
  }
2737
3533
 
2738
3534
  // src/plan-loop/plan-state.ts
2739
- import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
2740
- import path26 from "path";
3535
+ import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
3536
+ import path25 from "path";
2741
3537
  function countPendingTodos(raw) {
2742
3538
  const lines = raw.split(/\r?\n/);
2743
3539
  let inFront = 0;
@@ -2765,18 +3561,18 @@ function countPendingTodos(raw) {
2765
3561
  async function findActivePlanFile(plansDir) {
2766
3562
  if (!await fileExists(plansDir)) return null;
2767
3563
  const files = (await readdir3(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
2768
- return files[0] ? path26.join(plansDir, files[0]) : null;
3564
+ return files[0] ? path25.join(plansDir, files[0]) : null;
2769
3565
  }
2770
3566
  async function readPlan(planPath) {
2771
- return readFile8(planPath, "utf8");
3567
+ return readFile12(planPath, "utf8");
2772
3568
  }
2773
3569
 
2774
3570
  // src/plan-loop/sentinel.ts
2775
- import { readFile as readFile9 } from "fs/promises";
3571
+ import { readFile as readFile13 } from "fs/promises";
2776
3572
  var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
2777
- function takeFromText(text2) {
2778
- if (!text2) return null;
2779
- const m = SENTINEL_RE.exec(text2);
3573
+ function takeFromText(text) {
3574
+ if (!text) return null;
3575
+ const m = SENTINEL_RE.exec(text);
2780
3576
  if (!m?.[1]) return null;
2781
3577
  const raw = m[1].trim();
2782
3578
  if (raw.toLowerCase().startsWith("stop")) {
@@ -2819,7 +3615,7 @@ function parseSentinelFromLog(content) {
2819
3615
  }
2820
3616
  async function parseSentinelFromLogFile(logPath) {
2821
3617
  try {
2822
- const content = await readFile9(logPath, "utf8");
3618
+ const content = await readFile13(logPath, "utf8");
2823
3619
  return parseSentinelFromLog(content);
2824
3620
  } catch {
2825
3621
  return { kind: "missing" };
@@ -2832,7 +3628,7 @@ function formatSentinelLine(sentinel) {
2832
3628
  }
2833
3629
 
2834
3630
  // src/plan-loop/skin-banners.ts
2835
- import path27 from "path";
3631
+ import path26 from "path";
2836
3632
  import {
2837
3633
  blue,
2838
3634
  cyan as cyan2,
@@ -2868,7 +3664,7 @@ function resolveColor(name, fallback) {
2868
3664
  async function resolveCliSkinId(root) {
2869
3665
  try {
2870
3666
  const cfg = await readJson(
2871
- path27.join(root, ".cursor", "context", "config.json")
3667
+ path26.join(root, ".cursor", "context", "config.json")
2872
3668
  );
2873
3669
  const id = cfg?.workspaceSkin?.modes?.[CLI_RUN_PLAN_MODE];
2874
3670
  if (typeof id === "string" && id.trim()) return id.trim();
@@ -2878,7 +3674,7 @@ async function resolveCliSkinId(root) {
2878
3674
  }
2879
3675
  async function loadSkinPack(root, skinId) {
2880
3676
  try {
2881
- const skinPath = path27.join(root, "registry", "skins", "core", skinId, "skin.json");
3677
+ const skinPath = path26.join(root, "registry", "skins", "core", skinId, "skin.json");
2882
3678
  const pack = await readJson(skinPath);
2883
3679
  if (!pack || typeof pack.id !== "string") return null;
2884
3680
  return pack;
@@ -2937,9 +3733,9 @@ function sleep(ms) {
2937
3733
  return new Promise((r) => setTimeout(r, ms));
2938
3734
  }
2939
3735
  async function runPlanLoop(opts) {
2940
- const plansDir = path28.join(opts.root, ".cursor", "plans");
2941
- const stopFile = path28.join(opts.root, ".cursor", "loop.stop");
2942
- const logDir = path28.join(opts.root, ".cursor", "loop-logs");
3736
+ const plansDir = path27.join(opts.root, ".cursor", "plans");
3737
+ const stopFile = path27.join(opts.root, ".cursor", "loop.stop");
3738
+ const logDir = path27.join(opts.root, ".cursor", "loop-logs");
2943
3739
  const planPath = await findActivePlanFile(plansDir);
2944
3740
  if (!planPath) {
2945
3741
  logger.error("No active plan in .cursor/plans/");
@@ -2948,7 +3744,7 @@ async function runPlanLoop(opts) {
2948
3744
  const pending = async () => countPendingTodos(await readPlan(planPath));
2949
3745
  await mkdir4(logDir, { recursive: true });
2950
3746
  try {
2951
- await unlink(stopFile);
3747
+ await unlink2(stopFile);
2952
3748
  } catch {
2953
3749
  }
2954
3750
  const onSigInt = () => {
@@ -2960,7 +3756,7 @@ async function runPlanLoop(opts) {
2960
3756
  try {
2961
3757
  const skin = await loadCliRunPlanSkin(opts.root);
2962
3758
  const banners = createSkinBannerPrinter(skin);
2963
- console.log(`Active plan: ${path28.basename(planPath)}`);
3759
+ console.log(`Active plan: ${path27.basename(planPath)}`);
2964
3760
  console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
2965
3761
  console.log(`Backend: ${opts.backend.id}`);
2966
3762
  if (skin) {
@@ -3003,8 +3799,8 @@ async function runPlanLoop(opts) {
3003
3799
  planExhausted = true;
3004
3800
  break;
3005
3801
  }
3006
- const logPath = path28.join(logDir, `tick-${stamp()}.log`);
3007
- const relLog = path28.relative(opts.root, logPath);
3802
+ const logPath = path27.join(logDir, `tick-${stamp()}.log`);
3803
+ const relLog = path27.relative(opts.root, logPath);
3008
3804
  console.log("");
3009
3805
  const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
3010
3806
  if (banners) banners.tickStart(tickLine);
@@ -3023,7 +3819,7 @@ async function runPlanLoop(opts) {
3023
3819
  return 1;
3024
3820
  }
3025
3821
  try {
3026
- const logText = await readFile10(logPath, "utf8");
3822
+ const logText = await readFile14(logPath, "utf8");
3027
3823
  if (logText.includes("Too many MCP tools")) {
3028
3824
  const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
3029
3825
  if (banners) banners.stop(msg);
@@ -3080,7 +3876,7 @@ async function runPlanLoop(opts) {
3080
3876
  const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
3081
3877
  if (banners) banners.phaseComplete(finishDetail);
3082
3878
  console.log(
3083
- `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path28.relative(opts.root, logDir)}/`
3879
+ `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path27.relative(opts.root, logDir)}/`
3084
3880
  );
3085
3881
  if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
3086
3882
  await armExternalPlanReview(opts.root);
@@ -3092,7 +3888,7 @@ async function runPlanLoop(opts) {
3092
3888
  }
3093
3889
 
3094
3890
  // src/commands/run-plan.ts
3095
- var runPlanCommand = defineCommand7({
3891
+ var runPlanCommand = defineCommand8({
3096
3892
  meta: {
3097
3893
  name: "run-plan",
3098
3894
  description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
@@ -3151,7 +3947,7 @@ var runPlanCommand = defineCommand7({
3151
3947
  return;
3152
3948
  }
3153
3949
  const code = await runPlanLoop({
3154
- root: path29.resolve(args.cwd),
3950
+ root: path28.resolve(args.cwd),
3155
3951
  maxTicks,
3156
3952
  sleepSeconds,
3157
3953
  model: args.model ? String(args.model) : void 0,
@@ -3163,8 +3959,8 @@ var runPlanCommand = defineCommand7({
3163
3959
  });
3164
3960
 
3165
3961
  // src/commands/scan.ts
3166
- import { defineCommand as defineCommand8 } from "citty";
3167
- var scanCommand = defineCommand8({
3962
+ import { defineCommand as defineCommand9 } from "citty";
3963
+ var scanCommand = defineCommand9({
3168
3964
  meta: {
3169
3965
  name: "scan",
3170
3966
  description: "Scan the current repository and print detected profile."
@@ -3185,9 +3981,21 @@ var scanCommand = defineCommand8({
3185
3981
  });
3186
3982
 
3187
3983
  // src/commands/status.ts
3188
- import path30 from "path";
3189
- import { defineCommand as defineCommand9 } from "citty";
3190
- var statusCommand = defineCommand9({
3984
+ import path29 from "path";
3985
+ import { defineCommand as defineCommand10 } from "citty";
3986
+ function profileStatus(profile) {
3987
+ if (!profile) return { origin: "none", evidence: [], profile: null };
3988
+ if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
3989
+ const detection = profile.detection;
3990
+ return {
3991
+ origin: "readiness-scanner",
3992
+ evidence: detection.providerEvidence ?? [],
3993
+ profile
3994
+ };
3995
+ }
3996
+ return { origin: "legacy-wizard", evidence: [], profile };
3997
+ }
3998
+ var statusCommand = defineCommand10({
3191
3999
  meta: {
3192
4000
  name: "status",
3193
4001
  description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
@@ -3204,15 +4012,26 @@ var statusCommand = defineCommand9({
3204
4012
  }
3205
4013
  },
3206
4014
  async run({ args }) {
3207
- const manifest = await loadAgentKitManifest(args.cwd);
3208
- const profilePath = path30.join(args.cwd, ".cursor", "agent-kit.config.json");
3209
- const profile = await readJson(profilePath);
4015
+ const rootDir = path29.resolve(args.cwd);
4016
+ const [manifest, rawProfile, scan] = await Promise.all([
4017
+ loadAgentKitManifest(rootDir),
4018
+ readJson(
4019
+ path29.join(rootDir, ".cursor", "agent-kit.config.json")
4020
+ ),
4021
+ runScanner(rootDir)
4022
+ ]);
4023
+ const readiness = createReadinessReport(scan, { generatorVersion: KIT_VERSION });
4024
+ const profile = profileStatus(rawProfile);
4025
+ const nextAction = readiness.pendingActions[0];
3210
4026
  if (args.json) {
3211
4027
  console.log(
3212
4028
  JSON.stringify(
3213
4029
  {
4030
+ runtimeVersion: KIT_VERSION,
3214
4031
  manifest: manifest ?? null,
3215
- profile: profile ?? null
4032
+ readiness,
4033
+ pendingActions: readiness.pendingActions,
4034
+ profile
3216
4035
  },
3217
4036
  null,
3218
4037
  2
@@ -3221,11 +4040,12 @@ var statusCommand = defineCommand9({
3221
4040
  return;
3222
4041
  }
3223
4042
  if (!manifest) {
3224
- logger.warn(`No ${MANIFEST_RELATIVE_PATH} \u2014 run agent-kit install.`);
4043
+ logger.warn(`No ${MANIFEST_RELATIVE_PATH}: run agent-kit install.`);
3225
4044
  } else {
3226
4045
  const protectedGlobs = resolveProtectedGlobs(manifest);
3227
4046
  console.log("Agent Kit status");
3228
- console.log(` version: ${manifest.version}`);
4047
+ console.log(` runtime: ${KIT_VERSION}`);
4048
+ console.log(` installed: ${manifest.version}`);
3229
4049
  console.log(` profile: ${manifest.profile ?? "(none)"}`);
3230
4050
  console.log(` packs: ${(manifest.packs ?? []).join(", ") || "(none)"}`);
3231
4051
  console.log(` skills: ${(manifest.skills ?? []).length} listed`);
@@ -3233,21 +4053,26 @@ var statusCommand = defineCommand9({
3233
4053
  console.log(
3234
4054
  ` registry: ${manifest.registry?.url ?? "(default)"} @ ${manifest.registry?.ref ?? "(default)"}`
3235
4055
  );
3236
- if (manifest.installedAt) console.log(` installed: ${manifest.installedAt}`);
3237
- }
3238
- if (profile) {
3239
- console.log("Wizard profile (agent-kit.config.json): present");
3240
- console.log(` IDE: ${profile.ide?.ide ?? "?"}`);
3241
- console.log(` core picks: ${(profile.selectedCoreComponents ?? []).join(", ") || "(none)"}`);
3242
- } else {
3243
- logger.info("No wizard profile \u2014 optional; run agent-kit init for generators.");
4056
+ if (manifest.installedAt) console.log(` installed at: ${manifest.installedAt}`);
3244
4057
  }
4058
+ console.log("Repository readiness");
4059
+ console.log(
4060
+ ` ready: ${readiness.summary.ready}, choices: ${readiness.summary.needs_choice}, manual: ${readiness.summary.manual}, blocked: ${readiness.summary.blocked}`
4061
+ );
4062
+ console.log(` pending: ${readiness.pendingActions.length}`);
4063
+ console.log(` profile origin: ${profile.origin}`);
4064
+ console.log(
4065
+ ` profile evidence: ${profile.evidence.map((item) => item.value).join(", ") || "(none)"}`
4066
+ );
4067
+ console.log(
4068
+ nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
4069
+ );
3245
4070
  }
3246
4071
  });
3247
4072
 
3248
4073
  // src/commands/update.ts
3249
- import { defineCommand as defineCommand10 } from "citty";
3250
- var updateCommand = defineCommand10({
4074
+ import { defineCommand as defineCommand11 } from "citty";
4075
+ var updateCommand = defineCommand11({
3251
4076
  meta: {
3252
4077
  name: "update",
3253
4078
  description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths."
@@ -3292,7 +4117,7 @@ var updateCommand = defineCommand10({
3292
4117
  });
3293
4118
 
3294
4119
  // src/index.ts
3295
- var main = defineCommand11({
4120
+ var main = defineCommand12({
3296
4121
  meta: {
3297
4122
  name: "agent-kit",
3298
4123
  description: "HITL framework for AI-assisted IDEs"
@@ -3302,6 +4127,7 @@ var main = defineCommand11({
3302
4127
  install: installCommand,
3303
4128
  scan: scanCommand,
3304
4129
  add: addCommand,
4130
+ doctor: doctorCommand,
3305
4131
  status: statusCommand,
3306
4132
  update: updateCommand,
3307
4133
  diff: diffCommand,