@dadado/agent-kit-cli 4.4.7 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1800 -1037
  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";
@@ -66,12 +66,12 @@ async function listDirectory(rootDir) {
66
66
  import path2 from "path";
67
67
  function resolveContained(root, rel) {
68
68
  const rootAbs = path2.resolve(root);
69
- const candidate = path2.resolve(rootAbs, rel);
70
- const relToRoot = path2.relative(rootAbs, candidate);
69
+ const candidate2 = path2.resolve(rootAbs, rel);
70
+ const relToRoot = path2.relative(rootAbs, candidate2);
71
71
  if (relToRoot.startsWith("..") || path2.isAbsolute(relToRoot)) {
72
72
  throw new Error(`Path escapes registry/project root: ${rel}`);
73
73
  }
74
- return candidate;
74
+ return candidate2;
75
75
  }
76
76
  function toPosixRel(root, absPath) {
77
77
  return path2.relative(root, absPath).split(path2.sep).join("/");
@@ -152,10 +152,19 @@ function isProtectedPath(relPath, globs) {
152
152
 
153
153
  // src/lifecycle/apply.ts
154
154
  function emptyStats() {
155
- return { written: [], skippedProtected: [], missing: [], unchanged: [] };
155
+ return {
156
+ written: [],
157
+ removed: [],
158
+ collisions: [],
159
+ skippedProtected: [],
160
+ missing: [],
161
+ unchanged: []
162
+ };
156
163
  }
157
164
  function mergeStats(into, from) {
158
165
  into.written.push(...from.written);
166
+ into.removed.push(...from.removed);
167
+ into.collisions.push(...from.collisions);
159
168
  into.skippedProtected.push(...from.skippedProtected);
160
169
  into.missing.push(...from.missing);
161
170
  into.unchanged.push(...from.unchanged);
@@ -221,6 +230,7 @@ function buildManifest(input) {
221
230
  if (input.profile) manifest.profile = input.profile;
222
231
  if (input.packs?.length) manifest.packs = [...new Set(input.packs)].sort();
223
232
  if (input.skills?.length) manifest.skills = [...new Set(input.skills)].sort();
233
+ if (input.personalization) manifest.personalization = input.personalization;
224
234
  if (input.registryUrl || input.registryRef) {
225
235
  manifest.registry = {};
226
236
  if (input.registryUrl) manifest.registry.url = input.registryUrl;
@@ -258,6 +268,14 @@ function logApplyStats(stats) {
258
268
  if (stats.unchanged.length > 0) {
259
269
  logger.info(`Unchanged: ${stats.unchanged.length}`);
260
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
+ }
261
279
  if (stats.skippedProtected.length > 0) {
262
280
  logger.warn(`Skipped protected (L3): ${stats.skippedProtected.length}`);
263
281
  for (const p of stats.skippedProtected) logger.info(` ~ ${p}`);
@@ -394,7 +412,21 @@ var REGISTRY_CLI_ARGS = {
394
412
  };
395
413
 
396
414
  // src/lifecycle/version.ts
397
- 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();
398
430
 
399
431
  // src/manifest/index.ts
400
432
  import path5 from "path";
@@ -518,6 +550,21 @@ function parseAgentKitManifest(raw) {
518
550
  }
519
551
  }
520
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
+ }
521
568
  if (rest.installedAt !== void 0 && typeof rest.installedAt !== "string") {
522
569
  issues.push("installedAt must be a string");
523
570
  }
@@ -530,6 +577,7 @@ function parseAgentKitManifest(raw) {
530
577
  "protected",
531
578
  "overrides",
532
579
  "registry",
580
+ "personalization",
533
581
  "installedAt"
534
582
  ]);
535
583
  for (const key of Object.keys(rest)) {
@@ -550,6 +598,7 @@ function parseAgentKitManifest(raw) {
550
598
  if (protectedPaths) manifest.protected = protectedPaths;
551
599
  if (overrides) manifest.overrides = overrides;
552
600
  if (registry) manifest.registry = registry;
601
+ if (personalization) manifest.personalization = personalization;
553
602
  if (typeof rest.installedAt === "string") manifest.installedAt = rest.installedAt;
554
603
  return manifest;
555
604
  }
@@ -912,6 +961,10 @@ var L0_ARTIFACTS = [
912
961
  target: ".cursor/rules/docs-professional-standard.mdc"
913
962
  },
914
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
+ },
915
968
  {
916
969
  source: "registry/rules/git-secrets-safety.mdc",
917
970
  target: ".cursor/rules/git-secrets-safety.mdc"
@@ -922,8 +975,8 @@ var L0_ARTIFACTS = [
922
975
  target: ".cursor/commands/start-project.md"
923
976
  },
924
977
  {
925
- source: ".cursor/commands/onboard.md",
926
- target: ".cursor/commands/onboard.md"
978
+ source: ".cursor/commands/agent-kit-onboard.md",
979
+ target: ".cursor/commands/agent-kit-onboard.md"
927
980
  },
928
981
  {
929
982
  source: ".cursor/commands/continue-plan.md",
@@ -962,6 +1015,26 @@ var L0_ARTIFACTS = [
962
1015
  source: ".cursor/context/templates/plan-external-review-prompt.md",
963
1016
  target: ".cursor/context/templates/plan-external-review-prompt.md"
964
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
+ },
965
1038
  {
966
1039
  source: ".cursor/context/templates/plan-monitor.md",
967
1040
  target: ".cursor/context/templates/plan-monitor.md"
@@ -1416,40 +1489,592 @@ var diffCommand = defineCommand3({
1416
1489
  }
1417
1490
  });
1418
1491
 
1419
- // src/commands/handoff.ts
1420
- import { spawn } from "child_process";
1421
- import { readFile as readFile6, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
1422
- import path10 from "path";
1492
+ // src/commands/doctor.ts
1493
+ import path19 from "path";
1423
1494
  import { defineCommand as defineCommand4 } from "citty";
1424
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
+
1425
2077
  // src/types.ts
1426
- var GIT_PLATFORM_META = {
1427
- github: {
1428
- cli: "gh",
1429
- prTerm: "Pull Request",
1430
- prCommand: "gh pr create",
1431
- ciDefault: "github-actions"
1432
- },
1433
- gitlab: {
1434
- cli: "glab",
1435
- prTerm: "Merge Request",
1436
- prCommand: "glab mr create",
1437
- ciDefault: "gitlab-ci"
1438
- },
1439
- bitbucket: {
1440
- cli: "bb",
1441
- prTerm: "Pull Request",
1442
- prCommand: "bb pr create",
1443
- ciDefault: "bitbucket-pipelines"
1444
- },
1445
- "azure-devops": {
1446
- cli: "az repos",
1447
- prTerm: "Pull Request",
1448
- prCommand: "az repos pr create",
1449
- ciDefault: "azure-pipelines"
1450
- },
1451
- gitea: { cli: "tea", prTerm: "Pull Request", prCommand: "tea pr create", ciDefault: "none" }
1452
- };
1453
2078
  var CI_PLATFORM_FILES = {
1454
2079
  "github-actions": ".github/workflows",
1455
2080
  "gitlab-ci": ".gitlab-ci.yml",
@@ -1474,73 +2099,650 @@ var PM_TOOL_LABELS = {
1474
2099
  none: "None"
1475
2100
  };
1476
2101
 
1477
- // src/commands/handoff.ts
1478
- function parsePlanFrontmatter(raw) {
1479
- const match = raw.match(/^---\n([\s\S]*?)\n---/);
1480
- if (!match?.[1]) return null;
1481
- const block = match[1];
1482
- const name = block.match(/^name:\s*(.+)$/m)?.[1]?.trim();
1483
- const todos = [];
1484
- const todoBlocks = block.matchAll(/- id:\s*(\S+)\s*\n\s*content:\s*(.+)\n\s*status:\s*(\S+)/g);
1485
- for (const m of todoBlocks) {
1486
- if (m[1] && m[2] && m[3]) {
1487
- 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);
1488
2112
  }
1489
2113
  }
1490
- 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 };
1491
2143
  }
1492
- async function findActivePlan(plansDir) {
1493
- if (!await fileExists(plansDir)) return null;
1494
- const files = (await readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
1495
- for (const file of files) {
1496
- const raw = await readFile6(path10.join(plansDir, file), "utf8");
1497
- const fm = parsePlanFrontmatter(raw);
1498
- if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
1499
- 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 {
1500
2166
  }
1501
2167
  }
1502
- return files[0] ? { file: files[0], raw: await readFile6(path10.join(plansDir, files[0]), "utf8") } : null;
1503
- }
1504
- function now() {
1505
- return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
1506
- }
1507
- async function loadProfile(rootDir) {
1508
- const configPath = path10.join(rootDir, ".cursor", "agent-kit.config.json");
1509
- try {
1510
- return await readJson(configPath);
1511
- } catch {
1512
- return null;
1513
- }
1514
- }
1515
- function buildRoutines(profile) {
1516
- const lines = [];
1517
- const workflow = profile?.git.workflow;
1518
- if (workflow === "homolog-prod") {
1519
- lines.push("- [ ] `git staging` - move changes to staging");
1520
- lines.push("- [ ] `git prod` - promote to production (after approval)");
1521
- } else {
1522
- lines.push("- [ ] Commit, push and open PR/MR");
1523
- }
1524
- const pmTools = profile?.services.projectManagement;
1525
- if (pmTools && pmTools.length > 0) {
1526
- const labels = pmTools.map((t) => PM_TOOL_LABELS[t]).join(", ");
1527
- lines.push(`- [ ] Update tasks in ${labels} (if applicable)`);
2168
+ if (await fileExists(path14.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
2169
+ tools.push("github-issues");
1528
2170
  }
1529
- lines.push("- [ ] Review CHANGELOG");
1530
- return lines;
1531
- }
1532
- function closingInstruction(profile) {
1533
- const pmTools = profile?.services.projectManagement;
1534
- if (pmTools && pmTools.length > 0) {
1535
- const labels = pmTools.map((t) => PM_TOOL_LABELS[t]).join(", ");
1536
- return `All todos completed. Check git staging/prod and update ${labels} if applicable.`;
2171
+ if (await fileExists(path14.join(rootDir, ".github", "projects"))) {
2172
+ tools.push("github-projects");
1537
2173
  }
1538
- return "All todos completed. Check commit/push and PR/MR.";
2174
+ return [...new Set(tools)];
1539
2175
  }
1540
- function buildHandoff(planFile, fm, profile) {
1541
- const completed = fm.todos?.filter((t) => t.status === "completed") ?? [];
1542
- const pending = fm.todos?.filter((t) => t.status === "pending") ?? [];
1543
- const inProgress = fm.todos?.filter((t) => t.status === "in_progress") ?? [];
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") {
2721
+ lines.push("- [ ] `git staging` - move changes to staging");
2722
+ lines.push("- [ ] `git prod` - promote to production (after approval)");
2723
+ } else {
2724
+ lines.push("- [ ] Commit, push and open PR/MR");
2725
+ }
2726
+ const pmTools = profile?.services.projectManagement;
2727
+ if (pmTools && pmTools.length > 0) {
2728
+ const labels = pmTools.map((t) => PM_TOOL_LABELS[t]).join(", ");
2729
+ lines.push(`- [ ] Update tasks in ${labels} (if applicable)`);
2730
+ }
2731
+ lines.push("- [ ] Review CHANGELOG");
2732
+ return lines;
2733
+ }
2734
+ function closingInstruction(profile) {
2735
+ const pmTools = profile?.services.projectManagement;
2736
+ if (pmTools && pmTools.length > 0) {
2737
+ const labels = pmTools.map((t) => PM_TOOL_LABELS[t]).join(", ");
2738
+ return `All todos completed. Check git staging/prod and update ${labels} if applicable.`;
2739
+ }
2740
+ return "All todos completed. Check commit/push and PR/MR.";
2741
+ }
2742
+ function buildHandoff(planFile, fm, profile) {
2743
+ const completed = fm.todos?.filter((t) => t.status === "completed") ?? [];
2744
+ const pending = fm.todos?.filter((t) => t.status === "pending") ?? [];
2745
+ const inProgress = fm.todos?.filter((t) => t.status === "in_progress") ?? [];
1544
2746
  const nextTodo = inProgress[0] ?? pending[0];
1545
2747
  const completedPhase = completed.length;
1546
2748
  const totalPhases = fm.todos?.length ?? 0;
@@ -1591,7 +2793,7 @@ function runCursorHandoff(scriptPath, cwd) {
1591
2793
  child.on("close", (code) => resolve(code ?? 1));
1592
2794
  });
1593
2795
  }
1594
- var handoffCommand = defineCommand4({
2796
+ var handoffCommand = defineCommand5({
1595
2797
  meta: {
1596
2798
  name: "handoff",
1597
2799
  description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
@@ -1605,15 +2807,15 @@ var handoffCommand = defineCommand4({
1605
2807
  },
1606
2808
  async run({ args }) {
1607
2809
  const profile = await loadProfile(args.cwd);
1608
- const plansDir = path10.join(args.cwd, ".cursor", "plans");
1609
- 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");
1610
2812
  const plan = await findActivePlan(plansDir);
1611
2813
  if (plan) {
1612
2814
  const fm = parsePlanFrontmatter(plan.raw);
1613
2815
  if (fm) {
1614
- await ensureDir(path10.join(args.cwd, ".cursor"));
2816
+ await ensureDir(path20.join(args.cwd, ".cursor"));
1615
2817
  const content = buildHandoff(plan.file, fm, profile);
1616
- await writeFile3(handoffPath, content, "utf8");
2818
+ await writeFile4(handoffPath, content, "utf8");
1617
2819
  logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
1618
2820
  logger.info(
1619
2821
  `Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
@@ -1631,7 +2833,7 @@ var handoffCommand = defineCommand4({
1631
2833
  }
1632
2834
  logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
1633
2835
  }
1634
- const scriptPath = path10.join(args.cwd, "cursor-handoff");
2836
+ const scriptPath = path20.join(args.cwd, "cursor-handoff");
1635
2837
  if (!await fileExists(scriptPath)) {
1636
2838
  printV3Guidance();
1637
2839
  return;
@@ -1646,878 +2848,330 @@ var handoffCommand = defineCommand4({
1646
2848
  try {
1647
2849
  const code = await runCursorHandoff(scriptPath, args.cwd);
1648
2850
  if (code !== 0) {
1649
- process.exitCode = code;
1650
- }
1651
- } catch (err) {
1652
- logger.warn(`Failed to execute cursor-handoff: ${String(err)}`);
1653
- printV3Guidance();
1654
- }
1655
- }
1656
- });
1657
-
1658
- // src/commands/init.ts
1659
- import path23 from "path";
1660
- import { intro, outro } from "@clack/prompts";
1661
- import { defineCommand as defineCommand5 } from "citty";
1662
-
1663
- // src/generator/index.ts
1664
- import { writeFile as writeFile9 } from "fs/promises";
1665
- import path16 from "path";
1666
-
1667
- // src/generator/agents-md.ts
1668
- import { writeFile as writeFile4 } from "fs/promises";
1669
- import path11 from "path";
1670
-
1671
- // src/generator/platform.ts
1672
- function gitProviderLabel(profile) {
1673
- const p = profile.git.provider;
1674
- if (!p || p === "other") return "Git";
1675
- const labels = {
1676
- github: "GitHub",
1677
- gitlab: "GitLab",
1678
- bitbucket: "Bitbucket",
1679
- "azure-devops": "Azure DevOps",
1680
- gitea: "Gitea"
1681
- };
1682
- return labels[p] ?? "Git";
1683
- }
1684
- function gitCliTool(profile) {
1685
- const p = profile.git.provider;
1686
- if (!p || p === "other") return "git";
1687
- return GIT_PLATFORM_META[p]?.cli ?? "git";
1688
- }
1689
- function prTerminology(profile) {
1690
- const p = profile.git.provider;
1691
- if (!p || p === "other") return "PR/MR";
1692
- return GIT_PLATFORM_META[p]?.prTerm ?? "PR/MR";
1693
- }
1694
- function pmToolsList(profile) {
1695
- const tools = profile.services.projectManagement;
1696
- if (!tools || tools.length === 0) return "";
1697
- return tools.map((t) => PM_TOOL_LABELS[t]).join(", ");
1698
- }
1699
- function pmRoutineLine(profile) {
1700
- const label = pmToolsList(profile);
1701
- if (!label) return "";
1702
- return `- Update tasks in ${label} (if applicable)`;
1703
- }
1704
- function ciLabel(profile) {
1705
- const labels = {
1706
- "github-actions": "GitHub Actions",
1707
- "gitlab-ci": "GitLab CI",
1708
- "azure-pipelines": "Azure Pipelines",
1709
- "bitbucket-pipelines": "Bitbucket Pipelines",
1710
- jenkins: "Jenkins",
1711
- circleci: "CircleCI",
1712
- travis: "Travis CI"
1713
- };
1714
- return labels[profile.infra.ci] ?? "";
1715
- }
1716
- function devopsFlowSummary(profile) {
1717
- const parts = [];
1718
- const provider = gitProviderLabel(profile);
1719
- parts.push(`Git: ${provider}`);
1720
- if (profile.git.workflow !== "unknown") {
1721
- const workflowLabels = {
1722
- "trunk-based": "trunk-based",
1723
- "feature-pr": `feature branch \u2192 ${prTerminology(profile)} \u2192 main`,
1724
- gitflow: "gitflow (develop/release/main)",
1725
- "homolog-prod": "staging \u2192 production"
1726
- };
1727
- parts.push(`Workflow: ${workflowLabels[profile.git.workflow] ?? profile.git.workflow}`);
1728
- }
1729
- const ci = ciLabel(profile);
1730
- if (ci) parts.push(`CI/CD: ${ci}`);
1731
- const pm = pmToolsList(profile);
1732
- if (pm) parts.push(`Project mgmt: ${pm}`);
1733
- return parts.join(" | ");
1734
- }
1735
-
1736
- // src/generator/agents-md.ts
1737
- async function generateAgentsMd(profile) {
1738
- const target = path11.join(profile.rootDir, "AGENTS.md");
1739
- const flow = devopsFlowSummary(profile);
1740
- const prTerm = prTerminology(profile);
1741
- const content = `# AGENTS.md
1742
-
1743
- Project configured with Agent Kit v3.
1744
-
1745
- ## Detected context
1746
- - Stack: ${profile.stack.language}${profile.stack.framework ? ` (${profile.stack.framework})` : ""}
1747
- - IDE: ${profile.ide.ide} (${profile.ide.plan})
1748
- - ${flow}
1749
-
1750
- ## Guidelines
1751
- 1. Prefer small, verifiable changes.
1752
- 2. Adapt response depth to user's IDE plan/model.
1753
- 3. Use /worktree for experiments, /best-of-n for critical decisions (Cursor 3.0).
1754
- 4. Security review before merge.
1755
- 5. Always create a ${prTerm} \u2014 never push directly to main.
1756
- `;
1757
- await writeFile4(target, content, "utf8");
1758
- }
1759
-
1760
- // src/generator/cursor.ts
1761
- import { writeFile as writeFile5 } from "fs/promises";
1762
- import path12 from "path";
1763
- function buildGitWorkflowRule(profile) {
1764
- const prTerm = prTerminology(profile);
1765
- const cli = gitCliTool(profile);
1766
- const provider = gitProviderLabel(profile);
1767
- const pm = pmToolsList(profile);
1768
- const lines = [
1769
- "# Git Workflow",
1770
- `- Platform: ${provider} | CLI: \`${cli}\``,
1771
- "- Use Conventional Commits (feat:, fix:, docs:, refactor:, chore:, perf:, test:).",
1772
- "- For risky changes, prefer /worktree (isolated git worktree).",
1773
- "- For comparing approaches, use /best-of-n."
1774
- ];
1775
- if (profile.git.workflow === "homolog-prod") {
1776
- lines.push(
1777
- "- Flow: development \u2192 staging \u2192 production.",
1778
- `- Create ${prTerm} targeting the staging branch; merge to main only via promotion.`,
1779
- "- NEVER commit directly to main."
1780
- );
1781
- } else if (profile.git.workflow === "feature-pr") {
1782
- lines.push(
1783
- `- Flow: feature branch \u2192 ${prTerm} \u2192 main.`,
1784
- `- Always create a ${prTerm} for review before merging.`
1785
- );
1786
- } else if (profile.git.workflow === "gitflow") {
1787
- lines.push(
1788
- "- Flow: feature \u2192 develop \u2192 release \u2192 main.",
1789
- `- Create ${prTerm} targeting develop for features, main for releases.`
1790
- );
1791
- }
1792
- if (pm) {
1793
- lines.push(`- After merge: update task status in ${pm} if integration is available.`);
1794
- }
1795
- return `${lines.join("\n")}
1796
- `;
1797
- }
1798
- function buildHandoffRule(profile) {
1799
- const pm = pmRoutineLine(profile);
1800
- const routines = ["- Suggest routines: git commit/push, review CHANGELOG."];
1801
- if (pm) routines.push(`${pm}.`);
1802
- return `# Handoff \u2014 State in File
1803
- - After completing each task: save .cursor/HANDOFF.md with progress and next steps.
1804
- - Update to-dos in the plan (.cursor/plans/) when completing or starting a task.
1805
- - One HANDOFF per project \u2014 source of truth for continuity.
1806
- ${routines.join("\n")}
1807
- - To resume: /continue-plan in a new conversation.
1808
- - Native features (summaries, /resume, transcripts, Agents Window) complement \u2014 not replace.
1809
- - Context at ~60%: save handoff and suggest new conversation.
1810
- `;
1811
- }
1812
- function cursorRulesByPlan(profile) {
1813
- if (profile.ide.plan === "cursor-free") {
1814
- return [
1815
- {
1816
- filename: "01-core.mdc",
1817
- content: `# Core Rule
1818
- - Respond directly and concisely.
1819
- - Focus on small changes and local validation.
1820
- - Size each task for ~50% of context window.
1821
- `
1822
- },
1823
- {
1824
- filename: "02-handoff.mdc",
1825
- content: buildHandoffRule(profile)
1826
- }
1827
- ];
1828
- }
1829
- return [
1830
- {
1831
- filename: "01-core.mdc",
1832
- content: `# Core Rule
1833
- - Execute tasks end-to-end whenever possible.
1834
- - Prioritize security, tests, and architectural consistency.
1835
- - Size each task for ~50% of context window.
1836
- `
1837
- },
1838
- {
1839
- filename: "02-git-workflow.mdc",
1840
- content: buildGitWorkflowRule(profile)
1841
- },
1842
- {
1843
- filename: "03-handoff.mdc",
1844
- content: buildHandoffRule(profile)
1845
- },
1846
- {
1847
- filename: "04-ide-guide.mdc",
1848
- content: `# Cursor Guide
1849
- - Use Agents Window for parallelism \u2014 each agent reads HANDOFF before acting.
1850
- - Use Await for long-running processes.
1851
- - Transcripts and @mentions for cross-reference.
1852
- - /worktree for risky changes.
1853
- - /best-of-n to compare approaches.
1854
- `
1855
- }
1856
- ];
1857
- }
1858
- async function generateCursorArtifacts(profile) {
1859
- const rulesDir = path12.join(profile.rootDir, ".cursor", "rules");
1860
- const skillsDir = path12.join(profile.rootDir, ".cursor", "skills", "core");
1861
- const agentsDir = path12.join(profile.rootDir, ".cursor", "agents");
1862
- const commandsDir = path12.join(profile.rootDir, ".cursor", "commands");
1863
- await Promise.all([
1864
- ensureDir(rulesDir),
1865
- ensureDir(skillsDir),
1866
- ensureDir(agentsDir),
1867
- ensureDir(commandsDir)
1868
- ]);
1869
- const rules = cursorRulesByPlan(profile);
1870
- await Promise.all(
1871
- rules.map((rule) => writeFile5(path12.join(rulesDir, rule.filename), rule.content, "utf8"))
1872
- );
1873
- const includeAgents = profile.ide.plan !== "cursor-free";
1874
- if (includeAgents) {
1875
- await writeFile5(
1876
- path12.join(agentsDir, "security-reviewer.md"),
1877
- "# Security Reviewer\n\nFocus on auth, PII, secrets, injection, and logging.\n",
1878
- "utf8"
1879
- );
1880
- }
1881
- const ci = ciLabel(profile);
1882
- const provider = gitProviderLabel(profile);
1883
- const statusLines = [
1884
- "# /agent-kit-status",
1885
- "",
1886
- "Show current profile and active components.",
1887
- "",
1888
- "## DevOps Flow",
1889
- `- Git: ${provider} (${profile.git.workflow})`
1890
- ];
1891
- if (ci) statusLines.push(`- CI/CD: ${ci}`);
1892
- const pm = pmToolsList(profile);
1893
- if (pm) statusLines.push(`- Project management: ${pm}`);
1894
- statusLines.push("");
1895
- await writeFile5(
1896
- path12.join(commandsDir, "agent-kit-status.md"),
1897
- `${statusLines.join("\n")}
1898
- `,
1899
- "utf8"
1900
- );
1901
- }
1902
-
1903
- // src/generator/git-hooks.ts
1904
- import { chmod, writeFile as writeFile6 } from "fs/promises";
1905
- import path13 from "path";
1906
- async function generateGitHooks(profile) {
1907
- if (!profile.installHooks) return;
1908
- const hooksDir = path13.join(profile.rootDir, ".git", "hooks");
1909
- await ensureDir(hooksDir);
1910
- const preCommitPath = path13.join(hooksDir, "pre-commit");
1911
- const preCommit = `#!/usr/bin/env bash
1912
- set -euo pipefail
1913
-
1914
- if command -v rg >/dev/null 2>&1; then
1915
- rg -n --hidden --glob '!node_modules/**' '(AKIA|BEGIN PRIVATE KEY|xoxb-)' . && {
1916
- echo "Potential secret detected. Commit blocked."
1917
- exit 1
1918
- } || true
1919
- fi
1920
- `;
1921
- await writeFile6(preCommitPath, preCommit, "utf8");
1922
- await chmod(preCommitPath, 493);
1923
- }
1924
-
1925
- // src/generator/vscode.ts
1926
- import { writeFile as writeFile7 } from "fs/promises";
1927
- import path14 from "path";
1928
- async function generateVSCodeArtifacts(profile) {
1929
- const vscodeDir = path14.join(profile.rootDir, ".vscode");
1930
- const githubDir = path14.join(profile.rootDir, ".github");
1931
- await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
1932
- await writeFile7(
1933
- path14.join(vscodeDir, "settings.json"),
1934
- `${JSON.stringify(
1935
- {
1936
- "editor.formatOnSave": true,
1937
- "editor.codeActionsOnSave": {
1938
- "source.fixAll": "explicit"
1939
- },
1940
- "files.autoSave": "afterDelay"
1941
- },
1942
- null,
1943
- 2
1944
- )}
1945
- `,
1946
- "utf8"
1947
- );
1948
- const provider = gitProviderLabel(profile);
1949
- const prTerm = prTerminology(profile);
1950
- await writeFile7(
1951
- path14.join(githubDir, "copilot-instructions.md"),
1952
- `# Copilot Instructions
1953
-
1954
- - Keep code changes small and testable.
1955
- - Use Conventional Commits (feat:, fix:, docs:, etc.).
1956
- - Prefer security-safe defaults.
1957
- - Git platform: ${provider}. Always create a ${prTerm} for review.
1958
- `,
1959
- "utf8"
1960
- );
1961
- if (profile.ide.plan === "vscode-pro") {
1962
- await writeFile7(
1963
- path14.join(vscodeDir, "security-review.agent.md"),
1964
- "# Security Review Agent\n\nSpecialized mode for security review.\n",
1965
- "utf8"
1966
- );
1967
- }
1968
- }
1969
-
1970
- // src/generator/windsurf.ts
1971
- import { writeFile as writeFile8 } from "fs/promises";
1972
- import path15 from "path";
1973
- async function generateWindsurfArtifacts(profile) {
1974
- const provider = gitProviderLabel(profile);
1975
- const prTerm = prTerminology(profile);
1976
- const content = `# Windsurf Rules
1977
- - Keep instructions concise and objective.
1978
- - Prefer small diffs and explicit validation.
1979
- - Apply security review before merge.
1980
- - Use Conventional Commits (feat:, fix:, docs:, etc.).
1981
- - Git: ${provider}. Create a ${prTerm} for every change.
1982
- `;
1983
- await writeFile8(path15.join(profile.rootDir, ".windsurfrules"), content, "utf8");
1984
- }
1985
-
1986
- // src/generator/index.ts
1987
- async function generateFromProfile(profile) {
1988
- await generateAgentsMd(profile);
1989
- await generateGitHooks(profile);
1990
- if (profile.ide.ide === "cursor" || profile.ide.plan.startsWith("cursor")) {
1991
- await generateCursorArtifacts(profile);
1992
- }
1993
- if (profile.ide.ide === "vscode" || profile.ide.plan.startsWith("vscode")) {
1994
- await generateVSCodeArtifacts(profile);
1995
- }
1996
- if (profile.ide.ide === "windsurf" || profile.ide.plan === "windsurf") {
1997
- await generateWindsurfArtifacts(profile);
1998
- }
1999
- if (profile.ide.plan === "default") {
2000
- await Promise.all([
2001
- generateCursorArtifacts(profile),
2002
- generateVSCodeArtifacts(profile),
2003
- generateWindsurfArtifacts(profile)
2004
- ]);
2005
- }
2006
- const pluginDir = path16.join(profile.rootDir, ".cursor-plugin");
2007
- await ensureDir(pluginDir);
2008
- await writeFile9(
2009
- path16.join(pluginDir, "plugin.json"),
2010
- `${JSON.stringify(
2011
- {
2012
- name: "agent-kit",
2013
- displayName: "Agent Kit",
2014
- author: "agent-kit-startup",
2015
- description: "Bootstrap de ambiente dev com IA (Cursor, VS Code, Windsurf)",
2016
- keywords: ["agents", "context", "automation", "multi-ide"],
2017
- license: "MIT",
2018
- version: "3.0.0"
2019
- },
2020
- null,
2021
- 2
2022
- )}
2023
- `,
2024
- "utf8"
2025
- );
2026
- }
2027
-
2028
- // src/scanner/scan.ts
2029
- import path22 from "path";
2030
-
2031
- // src/scanner/detect-git.ts
2032
- import { execFile as execFile2 } from "child_process";
2033
- import path17 from "path";
2034
- import { promisify as promisify2 } from "util";
2035
- var exec = promisify2(execFile2);
2036
- function detectProvider(remoteUrl) {
2037
- if (!remoteUrl) return void 0;
2038
- if (remoteUrl.includes("github")) return "github";
2039
- if (remoteUrl.includes("gitlab")) return "gitlab";
2040
- if (remoteUrl.includes("bitbucket")) return "bitbucket";
2041
- if (remoteUrl.includes("dev.azure.com") || remoteUrl.includes("visualstudio.com"))
2042
- return "azure-devops";
2043
- if (remoteUrl.includes("gitea") || remoteUrl.includes("codeberg")) return "gitea";
2044
- return "other";
2045
- }
2046
- function inferWorkflow(currentBranch) {
2047
- if (!currentBranch) return "unknown";
2048
- if (currentBranch === "main" || currentBranch === "master") return "feature-pr";
2049
- if (currentBranch.includes("develop") || currentBranch.includes("release")) return "gitflow";
2050
- if (currentBranch.includes("staging") || currentBranch.includes("homolog")) return "homolog-prod";
2051
- return "feature-pr";
2052
- }
2053
- async function runGit(args, rootDir) {
2054
- try {
2055
- const { stdout } = await exec("git", args, { cwd: rootDir });
2056
- return stdout.trim();
2057
- } catch {
2058
- return void 0;
2059
- }
2060
- }
2061
- async function detectGit(rootDir) {
2062
- const hasGit = await fileExists(path17.join(rootDir, ".git"));
2063
- if (!hasGit) return { workflow: "unknown" };
2064
- const remoteUrl = await runGit(["remote", "get-url", "origin"], rootDir);
2065
- const currentBranch = await runGit(["branch", "--show-current"], rootDir);
2066
- return {
2067
- provider: detectProvider(remoteUrl),
2068
- remoteUrl,
2069
- currentBranch,
2070
- workflow: inferWorkflow(currentBranch)
2071
- };
2072
- }
2073
-
2074
- // src/scanner/detect-ide.ts
2075
- import path18 from "path";
2076
- async function detectIde(rootDir) {
2077
- const hasCursor = await fileExists(path18.join(rootDir, ".cursor"));
2078
- const hasVSCode = await fileExists(path18.join(rootDir, ".vscode"));
2079
- const hasWindsurf = await fileExists(path18.join(rootDir, ".windsurfrules"));
2080
- if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
2081
- if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
2082
- if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
2083
- return { ide: "unknown", plan: "default" };
2084
- }
2085
-
2086
- // src/scanner/detect-infra.ts
2087
- import path19 from "path";
2088
- async function detectInfra(rootDir) {
2089
- const docker = await fileExists(path19.join(rootDir, "Dockerfile")) || await fileExists(path19.join(rootDir, "docker-compose.yml")) || await fileExists(path19.join(rootDir, "docker-compose.yaml"));
2090
- const kubernetes = await fileExists(path19.join(rootDir, "k8s")) || await fileExists(path19.join(rootDir, "kubernetes"));
2091
- let ci = "none";
2092
- for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
2093
- if (await fileExists(path19.join(rootDir, filePath))) {
2094
- ci = platform;
2095
- break;
2096
- }
2097
- }
2098
- return { docker, kubernetes, ci };
2099
- }
2100
-
2101
- // src/scanner/detect-services.ts
2102
- import { readFile as readFile7 } from "fs/promises";
2103
- import path20 from "path";
2104
- async function detectProjectManagement(rootDir) {
2105
- const tools = [];
2106
- const mcpConfigPaths = [
2107
- path20.join(rootDir, ".cursor", "mcp.json"),
2108
- path20.join(rootDir, "mcp.json")
2109
- ];
2110
- for (const configPath of mcpConfigPaths) {
2111
- if (!await fileExists(configPath)) continue;
2112
- try {
2113
- const raw = await readFile7(configPath, "utf8");
2114
- const lower = raw.toLowerCase();
2115
- if (lower.includes("clickup")) tools.push("clickup");
2116
- if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
2117
- if (lower.includes("linear")) tools.push("linear");
2118
- if (lower.includes("asana")) tools.push("asana");
2119
- if (lower.includes("youtrack")) tools.push("youtrack");
2120
- if (lower.includes("shortcut")) tools.push("shortcut");
2121
- } catch {
2851
+ process.exitCode = code;
2852
+ }
2853
+ } catch (err) {
2854
+ logger.warn(`Failed to execute cursor-handoff: ${String(err)}`);
2855
+ printV3Guidance();
2122
2856
  }
2123
2857
  }
2124
- if (await fileExists(path20.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
2125
- tools.push("github-issues");
2126
- }
2127
- if (await fileExists(path20.join(rootDir, ".github", "projects"))) {
2128
- tools.push("github-projects");
2129
- }
2130
- return [...new Set(tools)];
2131
- }
2132
- async function detectServices(rootDir) {
2133
- const hasPrisma = await fileExists(path20.join(rootDir, "prisma/schema.prisma"));
2134
- const hasSequelize = await fileExists(path20.join(rootDir, "sequelize"));
2135
- const hasDrizzle = await fileExists(path20.join(rootDir, "drizzle.config.ts"));
2136
- const hasKnex = await fileExists(path20.join(rootDir, "knexfile.ts"));
2137
- const hasTypeorm = await fileExists(path20.join(rootDir, "ormconfig.json"));
2138
- const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
2139
- const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
2140
- const projectManagement = await detectProjectManagement(rootDir);
2141
- return {
2142
- database,
2143
- orm,
2144
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2145
- };
2146
- }
2858
+ });
2147
2859
 
2148
- // src/scanner/detect-stack.ts
2860
+ // src/commands/init.ts
2861
+ import { intro, outro } from "@clack/prompts";
2862
+ import { defineCommand as defineCommand7 } from "citty";
2863
+
2864
+ // src/commands/install.ts
2865
+ import path23 from "path";
2866
+ import { defineCommand as defineCommand6 } from "citty";
2867
+
2868
+ // src/generator/personalization.ts
2869
+ import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
2149
2870
  import path21 from "path";
2150
- var PROJECT_MARKERS = [
2151
- "package.json",
2152
- "requirements.txt",
2153
- "pyproject.toml",
2154
- "go.mod",
2155
- "Gemfile",
2156
- "pom.xml",
2157
- "build.gradle",
2158
- "composer.json",
2159
- "Cargo.toml"
2160
- ];
2161
- async function detectStack(rootDir) {
2162
- const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path21.join(rootDir, item))))).some(Boolean);
2163
- const hasPackageJson = await fileExists(path21.join(rootDir, "package.json"));
2164
- if (hasPackageJson) {
2165
- 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"));
2166
- const hasNestConfig = await fileExists(path21.join(rootDir, "nest-cli.json"));
2167
- return {
2168
- language: "node",
2169
- framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
2170
- packageManager: "pnpm",
2171
- hasProjectFiles: hasAnyProjectMarker
2172
- };
2173
- }
2174
- if (await fileExists(path21.join(rootDir, "pyproject.toml"))) {
2175
- return { language: "python", framework: "python", hasProjectFiles: hasAnyProjectMarker };
2176
- }
2177
- if (await fileExists(path21.join(rootDir, "go.mod"))) {
2178
- return { language: "go", framework: "go", hasProjectFiles: hasAnyProjectMarker };
2179
- }
2180
- if (await fileExists(path21.join(rootDir, "Cargo.toml"))) {
2181
- return { language: "rust", framework: "rust", hasProjectFiles: hasAnyProjectMarker };
2182
- }
2183
- if (await fileExists(path21.join(rootDir, "composer.json"))) {
2184
- return { language: "php", framework: "php", hasProjectFiles: hasAnyProjectMarker };
2185
- }
2186
- return { language: "unknown", hasProjectFiles: hasAnyProjectMarker };
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
+ });
2187
2883
  }
2188
-
2189
- // src/scanner/scan.ts
2190
- var GREENFIELD_SAFE_FILES = /* @__PURE__ */ new Set([
2191
- ".git",
2192
- ".gitignore",
2193
- "LICENSE",
2194
- "README.md",
2195
- ".cursor",
2196
- ".vscode"
2197
- ]);
2198
- function isGreenfieldByEntries(entries) {
2199
- const meaningful = entries.filter((entry) => !GREENFIELD_SAFE_FILES.has(entry));
2200
- return meaningful.length === 0;
2884
+ function purposeEvidence(profile) {
2885
+ if (profile.purpose.confidence === "low") return [];
2886
+ return profile.purpose.evidence;
2201
2887
  }
2202
- async function runScanner(rootDir) {
2203
- const normalizedRoot = path22.resolve(rootDir);
2204
- const entries = await listDirectory(normalizedRoot);
2205
- const stack = await detectStack(normalizedRoot);
2206
- const isGreenfield = isGreenfieldByEntries(entries) || !stack.hasProjectFiles;
2207
- return {
2208
- rootDir: normalizedRoot,
2209
- isGreenfield,
2210
- stack,
2211
- git: await detectGit(normalizedRoot),
2212
- ide: await detectIde(normalizedRoot),
2213
- infra: await detectInfra(normalizedRoot),
2214
- services: await detectServices(normalizedRoot)
2215
- };
2888
+ function contextEvidence(profile, pattern) {
2889
+ return profile.context.sources.filter((item) => pattern.test(item.value));
2216
2890
  }
2217
-
2218
- // src/utils/prompts.ts
2219
- import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
2220
- var WORKSPACE_SKIN_MODE_DEFAULTS = {
2221
- default: "autopilot",
2222
- modes: {
2223
- "continue-plan": "autopilot",
2224
- "run-plan": "night-shift",
2225
- "cli-run-plan": "ghost-runner"
2226
- }
2227
- };
2228
- function workspaceSkinConfigFromChoice(choice) {
2229
- if (choice.kind === "skip") return null;
2230
- if (choice.kind === "mode-defaults")
2231
- return { ...WORKSPACE_SKIN_MODE_DEFAULTS, modes: { ...WORKSPACE_SKIN_MODE_DEFAULTS.modes } };
2232
- return {
2233
- default: choice.id,
2234
- modes: {
2235
- "continue-plan": choice.id,
2236
- "run-plan": choice.id,
2237
- "cli-run-plan": choice.id
2238
- }
2239
- };
2891
+ function readinessEvidence(report, checkId) {
2892
+ return report.pillars.flatMap((pillar2) => pillar2.checks).filter((check2) => check2.id === checkId).flatMap((check2) => check2.evidence);
2240
2893
  }
2241
- function ensureNotCancelled(value) {
2242
- if (isCancel(value)) {
2243
- cancel("Operation cancelled.");
2244
- process.exit(0);
2245
- }
2246
- return value;
2247
- }
2248
- async function askIdeAndPlan(current) {
2249
- const ide = ensureNotCancelled(
2250
- await select({
2251
- message: "Which is your main IDE?",
2252
- initialValue: current.ide === "unknown" ? void 0 : current.ide,
2253
- options: [
2254
- { label: "Cursor", value: "cursor" },
2255
- { label: "VS Code", value: "vscode" },
2256
- { label: "Windsurf", value: "windsurf" },
2257
- { label: "Other", value: "other" }
2258
- ]
2259
- })
2260
- );
2261
- const plan = ensureNotCancelled(
2262
- await select({
2263
- message: "What's your IDE plan?",
2264
- initialValue: current.plan === "default" ? void 0 : current.plan,
2265
- options: [
2266
- { label: "Cursor Free", value: "cursor-free" },
2267
- { label: "Cursor Pro / Business", value: "cursor-pro" },
2268
- { label: "VS Code + Copilot Free", value: "vscode-free" },
2269
- { label: "VS Code + Copilot Pro / Business", value: "vscode-pro" },
2270
- { label: "Windsurf", value: "windsurf" },
2271
- { label: "Don't know / default", value: "default" }
2272
- ]
2273
- })
2274
- );
2275
- return { ide, plan };
2276
- }
2277
- async function askGitWorkflow(current) {
2278
- return ensureNotCancelled(
2279
- await select({
2280
- message: "What's your Git workflow?",
2281
- initialValue: current === "unknown" ? void 0 : current,
2282
- options: [
2283
- { label: "trunk-based (direct to main)", value: "trunk-based" },
2284
- { label: "feature branch -> PR/MR -> main", value: "feature-pr" },
2285
- { label: "gitflow (develop/release/main)", value: "gitflow" },
2286
- { label: "staging -> production (staging -> main)", value: "homolog-prod" }
2287
- ]
2288
- })
2894
+ function hasPurpose(profile, purposes) {
2895
+ return purposes.some(
2896
+ (purpose) => profile.purpose.value === purpose || profile.purpose.categories.includes(purpose)
2289
2897
  );
2290
2898
  }
2291
- async function askProjectManagement(existing = []) {
2292
- return ensureNotCancelled(
2293
- await multiselect({
2294
- message: "Do you use any project management system?",
2295
- initialValues: existing,
2296
- options: [
2297
- { label: "GitHub Issues / Projects", value: "github-issues" },
2298
- { label: "Jira", value: "jira" },
2299
- { label: "Linear", value: "linear" },
2300
- { label: "ClickUp", value: "clickup" },
2301
- { label: "Azure Boards", value: "azure-boards" },
2302
- { label: "Asana", value: "asana" },
2303
- { label: "Trello", value: "trello" },
2304
- { label: "Shortcut", value: "shortcut" },
2305
- { label: "Notion", value: "notion" },
2306
- { label: "YouTrack", value: "youtrack" }
2307
- ],
2308
- required: false
2309
- })
2310
- );
2899
+ function candidate(kind, id, evidence, status) {
2900
+ const verified = uniqueEvidence(evidence);
2901
+ return verified.length > 0 ? { kind, id, status, evidence: verified } : null;
2311
2902
  }
2312
- async function askWorkspaceSkin() {
2313
- const value = ensureNotCancelled(
2314
- await select({
2315
- message: "Workspace skin for chat/CLI chrome? (optional)",
2316
- initialValue: "mode-defaults",
2317
- options: [
2318
- {
2319
- label: "Keep mode defaults (Autopilot/Night Shift/Ghost Runner per mode)",
2320
- value: "mode-defaults"
2321
- },
2322
- { label: "Autopilot", value: "autopilot" },
2323
- { label: "Night Shift", value: "night-shift" },
2324
- { label: "Ghost Runner", value: "ghost-runner" },
2325
- { label: "Skip for now", value: "skip" }
2326
- ]
2327
- })
2328
- );
2329
- if (value === "skip") return { kind: "skip" };
2330
- if (value === "mode-defaults") return { kind: "mode-defaults" };
2331
- return { kind: "skin", id: value };
2332
- }
2333
- async function runExistingProjectWizard(scan) {
2334
- const ide = await askIdeAndPlan(scan.ide);
2335
- const workflow = await askGitWorkflow(scan.git.workflow);
2336
- const projectManagement = await askProjectManagement(scan.services.projectManagement ?? []);
2337
- const installHooks = ensureNotCancelled(
2338
- await confirm({
2339
- message: "Install git hooks? (pre-commit: secrets + lint)",
2340
- initialValue: true
2341
- })
2342
- );
2343
- const workspaceSkinChoice = await askWorkspaceSkin();
2344
- return {
2345
- rootDir: scan.rootDir,
2346
- stack: scan.stack,
2347
- git: { ...scan.git, workflow },
2348
- ide,
2349
- infra: scan.infra,
2350
- services: {
2351
- ...scan.services,
2352
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2353
- },
2354
- installHooks,
2355
- selectedCoreComponents: [
2356
- "git-workflow",
2357
- "security-review",
2358
- "clean-code",
2359
- "docs-repo",
2360
- "ide-guide"
2361
- ],
2362
- workspaceSkinChoice
2363
- };
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
2909
+ );
2910
+ }
2911
+ return true;
2364
2912
  }
2365
- async function runGreenfieldWizard(scan) {
2366
- const language = ensureNotCancelled(
2367
- await select({
2368
- message: "What will be the main stack?",
2369
- options: [
2370
- { label: "Node.js (JavaScript/TypeScript)", value: "node" },
2371
- { label: "Python", value: "python" },
2372
- { label: "Go", value: "go" },
2373
- { label: "Ruby", value: "ruby" },
2374
- { label: "Java / Kotlin", value: "java" },
2375
- { label: "PHP", value: "php" },
2376
- { label: "Rust", value: "rust" },
2377
- { label: "C# / .NET", value: "dotnet" },
2378
- { label: "Other", value: "other" }
2379
- ]
2380
- })
2381
- );
2382
- const framework = ensureNotCancelled(
2383
- await text({
2384
- message: "Main framework? (ex: nextjs, nestjs, django, rails, spring, none)",
2385
- placeholder: "none",
2386
- initialValue: "none",
2387
- validate(value) {
2388
- return value.trim().length === 0 ? "Enter a framework or 'none'" : void 0;
2389
- }
2390
- })
2391
- );
2392
- const database = ensureNotCancelled(
2393
- await select({
2394
- message: "Will you use a database?",
2395
- options: [
2396
- { label: "PostgreSQL", value: "postgresql" },
2397
- { label: "MySQL", value: "mysql" },
2398
- { label: "MongoDB", value: "mongodb" },
2399
- { label: "SQLite", value: "sqlite" },
2400
- { label: "SQL Server", value: "sqlserver" },
2401
- { label: "None for now", value: "none" }
2402
- ]
2403
- })
2404
- );
2405
- const orm = ensureNotCancelled(
2406
- await text({
2407
- message: "ORM / query builder?",
2408
- placeholder: "prisma, drizzle, sequelize, typeorm, none",
2409
- initialValue: "none"
2410
- })
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))
2920
+ ];
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"));
2934
+ }
2935
+ if (profile.stack.language.toLowerCase() === "node" && packageEvidence.length > 0) {
2936
+ items.push(candidate("skill", "cursor-skills-node", packageEvidence, "applied"));
2937
+ }
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"));
2942
+ }
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
+ );
2954
+ }
2955
+ if (promptEvidence.length > 0) {
2956
+ items.push(candidate("skill", "prompts-markdown", promptEvidence, "applied"));
2957
+ }
2958
+ if (infraEvidence.length > 0) {
2959
+ items.push(candidate("pack", "devops", infraEvidence, "applied"));
2960
+ }
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"));
2964
+ }
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
+ );
2975
+ }
2976
+ const safetyEvidence = readinessEvidence(report, "safety.secrets").filter(
2977
+ (item) => item.value.startsWith("tracked:")
2411
2978
  );
2412
- const ide = await askIdeAndPlan(scan.ide);
2413
- const workflow = await askGitWorkflow(scan.git.workflow);
2414
- const projectManagement = await askProjectManagement();
2415
- const installHooks = ensureNotCancelled(
2416
- await confirm({
2417
- message: "Install git hooks? (pre-commit: secrets + lint)",
2418
- initialValue: true
2419
- })
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
+ )
2420
2989
  );
2421
- const workspaceSkinChoice = await askWorkspaceSkin();
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
+ `;
3019
+ }
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");
2422
3033
  return {
2423
- rootDir: scan.rootDir,
2424
- stack: {
2425
- language,
2426
- framework: framework === "none" ? void 0 : framework,
2427
- hasProjectFiles: false
2428
- },
2429
- git: {
2430
- ...scan.git,
2431
- workflow
2432
- },
2433
- ide,
2434
- infra: scan.infra,
2435
- services: {
2436
- database: database === "none" ? void 0 : database,
2437
- orm: orm === "none" ? void 0 : orm,
2438
- projectManagement: projectManagement.length > 0 ? projectManagement : void 0
2439
- },
2440
- installHooks,
2441
- selectedCoreComponents: [
2442
- "git-workflow",
2443
- "security-review",
2444
- "clean-code",
2445
- "docs-repo",
2446
- "ide-guide"
2447
- ],
2448
- workspaceSkinChoice
3034
+ kind: "file",
3035
+ id: relativePath,
3036
+ path: relativePath,
3037
+ status: "applied",
3038
+ evidence
2449
3039
  };
2450
3040
  }
2451
-
2452
- // src/commands/init.ts
2453
- async function mergeWorkspaceSkinConfig(rootDir, workspaceSkin) {
2454
- const contextDir = path23.join(rootDir, ".cursor", "context");
2455
- const configPath = path23.join(contextDir, "config.json");
2456
- await ensureDir(contextDir);
2457
- const existing = await readJson(configPath) ?? {};
2458
- await writeJson(configPath, { ...existing, workspaceSkin });
2459
- return configPath;
2460
- }
2461
- var initCommand = defineCommand5({
2462
- meta: {
2463
- name: "init",
2464
- description: "Run scanner + wizard + generation setup."
2465
- },
2466
- args: {
2467
- cwd: {
2468
- type: "string",
2469
- description: "Project root directory",
2470
- default: process.cwd()
3041
+ async function packTargets(registryRoot, packId) {
3042
+ const manifest = await loadPackManifest(registryRoot, packId);
3043
+ return manifest.members.map((member) => packMemberTargets(member).targetRel);
3044
+ }
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;
2471
3063
  }
2472
- },
2473
- async run({ args }) {
2474
- intro("agent-kit v3.0.0");
2475
- const scan = await runScanner(args.cwd);
2476
- logger.info(`Root: ${scan.rootDir}`);
2477
- logger.info(
2478
- scan.isGreenfield ? "Empty repo detected (greenfield)." : "Existing project detected."
2479
- );
2480
- const profile = scan.isGreenfield ? await runGreenfieldWizard(scan) : await runExistingProjectWizard(scan);
2481
- const { workspaceSkinChoice, ...profileToSave } = profile;
2482
- const configPath = path23.join(scan.rootDir, ".cursor", "agent-kit.config.json");
2483
- await writeJson(configPath, profileToSave);
2484
- logger.success(`Profile saved in ${configPath}`);
2485
- const skinConfig = workspaceSkinChoice !== void 0 ? workspaceSkinConfigFromChoice(workspaceSkinChoice) : null;
2486
- if (skinConfig) {
2487
- const contextConfigPath = await mergeWorkspaceSkinConfig(scan.rootDir, skinConfig);
2488
- logger.success(`Workspace skin saved in ${contextConfigPath}`);
2489
- }
2490
- await generateFromProfile(profile);
2491
- try {
2492
- const registry = await resolveRegistryRoot({ cwd: scan.rootDir });
2493
- const stats = await installSkillsByIds(
2494
- registry.root,
2495
- scan.rootDir,
2496
- 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"
2497
3076
  );
2498
- if (stats.written.length > 0) {
2499
- logger.success(`Skills installed: ${stats.written.join(", ")}`);
2500
- } else if (stats.missing.length > 0) {
2501
- logger.warn(`Skills not found in registry: ${stats.missing.join(", ")}`);
2502
- } else {
2503
- 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;
2504
3081
  }
2505
- } catch {
2506
- logger.info(
2507
- "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
2508
3098
  );
3099
+ continue;
2509
3100
  }
2510
- console.log("\nNext:");
2511
- console.log(` Open this folder in Cursor: ${scan.rootDir}`);
2512
- console.log(" Run /onboard in chat; then /start-project when you have a goal");
2513
- console.log(" Optional: agent-kit status");
2514
- outro("Setup completed.");
3101
+ componentResults.push(item);
2515
3102
  }
2516
- });
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
+ }
2517
3154
 
2518
- // src/commands/install.ts
2519
- import path24 from "path";
2520
- 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
+ }
2521
3175
 
2522
3176
  // src/lifecycle/sync.ts
2523
3177
  async function installL0(registryRoot, projectRoot, protectedGlobs) {
@@ -2532,6 +3186,12 @@ async function installL0(registryRoot, projectRoot, protectedGlobs) {
2532
3186
  );
2533
3187
  recordOutcome(stats, artifact.target, outcome);
2534
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
+ }
2535
3195
  return stats;
2536
3196
  }
2537
3197
  async function syncFromManifest(registryRoot, projectRoot, manifest) {
@@ -2561,11 +3221,73 @@ function parsePackList(raw) {
2561
3221
  )
2562
3222
  ];
2563
3223
  }
2564
- function printInstallNextSteps(projectRoot) {
2565
- console.log("\nNext:");
2566
- console.log(` Open this folder in Cursor: ${projectRoot}`);
2567
- console.log(" Run /onboard in chat; then /start-project when you have a goal");
2568
- 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
+ };
2569
3291
  }
2570
3292
  var installCommand = defineCommand6({
2571
3293
  meta: {
@@ -2589,50 +3311,61 @@ var installCommand = defineCommand6({
2589
3311
  ...REGISTRY_CLI_ARGS
2590
3312
  },
2591
3313
  async run({ args }) {
2592
- const projectRoot = path24.resolve(args.cwd);
3314
+ const projectRoot = path23.resolve(args.cwd);
2593
3315
  logger.info(`Installing into: ${projectRoot}`);
2594
3316
  const packs = parsePackList(args.pack);
2595
3317
  for (const id of packs) {
2596
3318
  if (!DOMAIN_PACK_IDS.includes(id)) {
2597
- 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.`);
2598
3320
  }
2599
3321
  }
2600
- const existing = await loadAgentKitManifest(projectRoot);
2601
- const registry = await resolveRegistryFromCli({
3322
+ const result = await performInstall({
2602
3323
  cwd: projectRoot,
3324
+ profile: args.profile,
3325
+ pack: args.pack,
2603
3326
  registry: args.registry,
2604
3327
  url: args.url,
2605
3328
  ref: args.ref,
2606
- refresh: args.refresh,
2607
- manifest: existing
2608
- });
2609
- logger.info(`Registry: ${registry.root} (${registry.source})`);
2610
- const draft = buildManifest({
2611
- version: KIT_VERSION,
2612
- profile: args.profile ?? existing?.profile ?? "default",
2613
- packs: packs.length > 0 ? packs : existing?.packs,
2614
- skills: existing?.skills,
2615
- protected: existing?.protected,
2616
- registryUrl: registry.url ?? existing?.registry?.url,
2617
- registryRef: registry.ref ?? existing?.registry?.ref
3329
+ refresh: args.refresh
2618
3330
  });
2619
- let stats;
2620
- if ((draft.packs?.length ?? 0) > 0 || (draft.skills?.length ?? 0) > 0) {
2621
- stats = await syncFromManifest(registry.root, projectRoot, draft);
2622
- } else {
2623
- const protectedGlobs = resolveProtectedGlobs(draft);
2624
- 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()
2625
3352
  }
2626
- const manifestPath = await saveManifest(projectRoot, draft);
2627
- logApplyStats(stats);
2628
- logger.success(`Manifest written: ${manifestPath}`);
2629
- 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
+ );
2630
3363
  }
2631
3364
  });
2632
3365
 
2633
3366
  // src/commands/run-plan.ts
2634
- import path29 from "path";
2635
- import { defineCommand as defineCommand7 } from "citty";
3367
+ import path28 from "path";
3368
+ import { defineCommand as defineCommand8 } from "citty";
2636
3369
 
2637
3370
  // src/plan-loop/backends.ts
2638
3371
  import { execFileSync, spawn as spawn2 } from "child_process";
@@ -2716,14 +3449,14 @@ function listBackendIds() {
2716
3449
  }
2717
3450
 
2718
3451
  // src/plan-loop/run-loop.ts
2719
- import { mkdir as mkdir4, readFile as readFile10, rm, unlink } from "fs/promises";
2720
- import path28 from "path";
3452
+ import { mkdir as mkdir4, readFile as readFile14, rm, unlink as unlink2 } from "fs/promises";
3453
+ import path27 from "path";
2721
3454
 
2722
3455
  // src/plan-loop/external-review.ts
2723
3456
  import { spawn as spawn3 } from "child_process";
2724
- import path25 from "path";
2725
- var CANONICAL_REL = path25.join(".cursor", "scripts", "plan-external-review.sh");
2726
- var FALLBACK_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");
2727
3460
  function isPlanExhaustedReason(reason) {
2728
3461
  const r = reason.trim().toLowerCase();
2729
3462
  if (!r) return false;
@@ -2745,8 +3478,8 @@ async function armExternalPlanReview(root, options = {}) {
2745
3478
  const existsFn = options.existsFn ?? fileExists;
2746
3479
  const log = options.log ?? ((line) => console.log(line));
2747
3480
  const force = options.force === true;
2748
- const canonicalPath = path25.join(root, CANONICAL_REL);
2749
- const fallbackPath = path25.join(root, FALLBACK_REL);
3481
+ const canonicalPath = path24.join(root, CANONICAL_REL);
3482
+ const fallbackPath = path24.join(root, FALLBACK_REL);
2750
3483
  let scriptPath = null;
2751
3484
  let scriptRel = CANONICAL_REL;
2752
3485
  if (await existsFn(canonicalPath)) {
@@ -2799,8 +3532,8 @@ async function armExternalPlanReview(root, options = {}) {
2799
3532
  }
2800
3533
 
2801
3534
  // src/plan-loop/plan-state.ts
2802
- import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
2803
- import path26 from "path";
3535
+ import { readFile as readFile12, readdir as readdir3 } from "fs/promises";
3536
+ import path25 from "path";
2804
3537
  function countPendingTodos(raw) {
2805
3538
  const lines = raw.split(/\r?\n/);
2806
3539
  let inFront = 0;
@@ -2828,18 +3561,18 @@ function countPendingTodos(raw) {
2828
3561
  async function findActivePlanFile(plansDir) {
2829
3562
  if (!await fileExists(plansDir)) return null;
2830
3563
  const files = (await readdir3(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
2831
- return files[0] ? path26.join(plansDir, files[0]) : null;
3564
+ return files[0] ? path25.join(plansDir, files[0]) : null;
2832
3565
  }
2833
3566
  async function readPlan(planPath) {
2834
- return readFile8(planPath, "utf8");
3567
+ return readFile12(planPath, "utf8");
2835
3568
  }
2836
3569
 
2837
3570
  // src/plan-loop/sentinel.ts
2838
- import { readFile as readFile9 } from "fs/promises";
3571
+ import { readFile as readFile13 } from "fs/promises";
2839
3572
  var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
2840
- function takeFromText(text2) {
2841
- if (!text2) return null;
2842
- const m = SENTINEL_RE.exec(text2);
3573
+ function takeFromText(text) {
3574
+ if (!text) return null;
3575
+ const m = SENTINEL_RE.exec(text);
2843
3576
  if (!m?.[1]) return null;
2844
3577
  const raw = m[1].trim();
2845
3578
  if (raw.toLowerCase().startsWith("stop")) {
@@ -2882,7 +3615,7 @@ function parseSentinelFromLog(content) {
2882
3615
  }
2883
3616
  async function parseSentinelFromLogFile(logPath) {
2884
3617
  try {
2885
- const content = await readFile9(logPath, "utf8");
3618
+ const content = await readFile13(logPath, "utf8");
2886
3619
  return parseSentinelFromLog(content);
2887
3620
  } catch {
2888
3621
  return { kind: "missing" };
@@ -2895,7 +3628,7 @@ function formatSentinelLine(sentinel) {
2895
3628
  }
2896
3629
 
2897
3630
  // src/plan-loop/skin-banners.ts
2898
- import path27 from "path";
3631
+ import path26 from "path";
2899
3632
  import {
2900
3633
  blue,
2901
3634
  cyan as cyan2,
@@ -2931,7 +3664,7 @@ function resolveColor(name, fallback) {
2931
3664
  async function resolveCliSkinId(root) {
2932
3665
  try {
2933
3666
  const cfg = await readJson(
2934
- path27.join(root, ".cursor", "context", "config.json")
3667
+ path26.join(root, ".cursor", "context", "config.json")
2935
3668
  );
2936
3669
  const id = cfg?.workspaceSkin?.modes?.[CLI_RUN_PLAN_MODE];
2937
3670
  if (typeof id === "string" && id.trim()) return id.trim();
@@ -2941,7 +3674,7 @@ async function resolveCliSkinId(root) {
2941
3674
  }
2942
3675
  async function loadSkinPack(root, skinId) {
2943
3676
  try {
2944
- const skinPath = path27.join(root, "registry", "skins", "core", skinId, "skin.json");
3677
+ const skinPath = path26.join(root, "registry", "skins", "core", skinId, "skin.json");
2945
3678
  const pack = await readJson(skinPath);
2946
3679
  if (!pack || typeof pack.id !== "string") return null;
2947
3680
  return pack;
@@ -3000,9 +3733,9 @@ function sleep(ms) {
3000
3733
  return new Promise((r) => setTimeout(r, ms));
3001
3734
  }
3002
3735
  async function runPlanLoop(opts) {
3003
- const plansDir = path28.join(opts.root, ".cursor", "plans");
3004
- const stopFile = path28.join(opts.root, ".cursor", "loop.stop");
3005
- 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");
3006
3739
  const planPath = await findActivePlanFile(plansDir);
3007
3740
  if (!planPath) {
3008
3741
  logger.error("No active plan in .cursor/plans/");
@@ -3011,7 +3744,7 @@ async function runPlanLoop(opts) {
3011
3744
  const pending = async () => countPendingTodos(await readPlan(planPath));
3012
3745
  await mkdir4(logDir, { recursive: true });
3013
3746
  try {
3014
- await unlink(stopFile);
3747
+ await unlink2(stopFile);
3015
3748
  } catch {
3016
3749
  }
3017
3750
  const onSigInt = () => {
@@ -3023,7 +3756,7 @@ async function runPlanLoop(opts) {
3023
3756
  try {
3024
3757
  const skin = await loadCliRunPlanSkin(opts.root);
3025
3758
  const banners = createSkinBannerPrinter(skin);
3026
- console.log(`Active plan: ${path28.basename(planPath)}`);
3759
+ console.log(`Active plan: ${path27.basename(planPath)}`);
3027
3760
  console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
3028
3761
  console.log(`Backend: ${opts.backend.id}`);
3029
3762
  if (skin) {
@@ -3066,8 +3799,8 @@ async function runPlanLoop(opts) {
3066
3799
  planExhausted = true;
3067
3800
  break;
3068
3801
  }
3069
- const logPath = path28.join(logDir, `tick-${stamp()}.log`);
3070
- const relLog = path28.relative(opts.root, logPath);
3802
+ const logPath = path27.join(logDir, `tick-${stamp()}.log`);
3803
+ const relLog = path27.relative(opts.root, logPath);
3071
3804
  console.log("");
3072
3805
  const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
3073
3806
  if (banners) banners.tickStart(tickLine);
@@ -3086,7 +3819,7 @@ async function runPlanLoop(opts) {
3086
3819
  return 1;
3087
3820
  }
3088
3821
  try {
3089
- const logText = await readFile10(logPath, "utf8");
3822
+ const logText = await readFile14(logPath, "utf8");
3090
3823
  if (logText.includes("Too many MCP tools")) {
3091
3824
  const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
3092
3825
  if (banners) banners.stop(msg);
@@ -3143,7 +3876,7 @@ async function runPlanLoop(opts) {
3143
3876
  const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
3144
3877
  if (banners) banners.phaseComplete(finishDetail);
3145
3878
  console.log(
3146
- `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)}/`
3147
3880
  );
3148
3881
  if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
3149
3882
  await armExternalPlanReview(opts.root);
@@ -3155,7 +3888,7 @@ async function runPlanLoop(opts) {
3155
3888
  }
3156
3889
 
3157
3890
  // src/commands/run-plan.ts
3158
- var runPlanCommand = defineCommand7({
3891
+ var runPlanCommand = defineCommand8({
3159
3892
  meta: {
3160
3893
  name: "run-plan",
3161
3894
  description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
@@ -3214,7 +3947,7 @@ var runPlanCommand = defineCommand7({
3214
3947
  return;
3215
3948
  }
3216
3949
  const code = await runPlanLoop({
3217
- root: path29.resolve(args.cwd),
3950
+ root: path28.resolve(args.cwd),
3218
3951
  maxTicks,
3219
3952
  sleepSeconds,
3220
3953
  model: args.model ? String(args.model) : void 0,
@@ -3226,8 +3959,8 @@ var runPlanCommand = defineCommand7({
3226
3959
  });
3227
3960
 
3228
3961
  // src/commands/scan.ts
3229
- import { defineCommand as defineCommand8 } from "citty";
3230
- var scanCommand = defineCommand8({
3962
+ import { defineCommand as defineCommand9 } from "citty";
3963
+ var scanCommand = defineCommand9({
3231
3964
  meta: {
3232
3965
  name: "scan",
3233
3966
  description: "Scan the current repository and print detected profile."
@@ -3248,9 +3981,21 @@ var scanCommand = defineCommand8({
3248
3981
  });
3249
3982
 
3250
3983
  // src/commands/status.ts
3251
- import path30 from "path";
3252
- import { defineCommand as defineCommand9 } from "citty";
3253
- 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({
3254
3999
  meta: {
3255
4000
  name: "status",
3256
4001
  description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
@@ -3267,15 +4012,26 @@ var statusCommand = defineCommand9({
3267
4012
  }
3268
4013
  },
3269
4014
  async run({ args }) {
3270
- const manifest = await loadAgentKitManifest(args.cwd);
3271
- const profilePath = path30.join(args.cwd, ".cursor", "agent-kit.config.json");
3272
- 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];
3273
4026
  if (args.json) {
3274
4027
  console.log(
3275
4028
  JSON.stringify(
3276
4029
  {
4030
+ runtimeVersion: KIT_VERSION,
3277
4031
  manifest: manifest ?? null,
3278
- profile: profile ?? null
4032
+ readiness,
4033
+ pendingActions: readiness.pendingActions,
4034
+ profile
3279
4035
  },
3280
4036
  null,
3281
4037
  2
@@ -3284,11 +4040,12 @@ var statusCommand = defineCommand9({
3284
4040
  return;
3285
4041
  }
3286
4042
  if (!manifest) {
3287
- logger.warn(`No ${MANIFEST_RELATIVE_PATH} \u2014 run agent-kit install.`);
4043
+ logger.warn(`No ${MANIFEST_RELATIVE_PATH}: run agent-kit install.`);
3288
4044
  } else {
3289
4045
  const protectedGlobs = resolveProtectedGlobs(manifest);
3290
4046
  console.log("Agent Kit status");
3291
- console.log(` version: ${manifest.version}`);
4047
+ console.log(` runtime: ${KIT_VERSION}`);
4048
+ console.log(` installed: ${manifest.version}`);
3292
4049
  console.log(` profile: ${manifest.profile ?? "(none)"}`);
3293
4050
  console.log(` packs: ${(manifest.packs ?? []).join(", ") || "(none)"}`);
3294
4051
  console.log(` skills: ${(manifest.skills ?? []).length} listed`);
@@ -3296,21 +4053,26 @@ var statusCommand = defineCommand9({
3296
4053
  console.log(
3297
4054
  ` registry: ${manifest.registry?.url ?? "(default)"} @ ${manifest.registry?.ref ?? "(default)"}`
3298
4055
  );
3299
- if (manifest.installedAt) console.log(` installed: ${manifest.installedAt}`);
3300
- }
3301
- if (profile) {
3302
- console.log("Wizard profile (agent-kit.config.json): present");
3303
- console.log(` IDE: ${profile.ide?.ide ?? "?"}`);
3304
- console.log(` core picks: ${(profile.selectedCoreComponents ?? []).join(", ") || "(none)"}`);
3305
- } else {
3306
- logger.info("No wizard profile \u2014 optional; run agent-kit init for generators.");
4056
+ if (manifest.installedAt) console.log(` installed at: ${manifest.installedAt}`);
3307
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
+ );
3308
4070
  }
3309
4071
  });
3310
4072
 
3311
4073
  // src/commands/update.ts
3312
- import { defineCommand as defineCommand10 } from "citty";
3313
- var updateCommand = defineCommand10({
4074
+ import { defineCommand as defineCommand11 } from "citty";
4075
+ var updateCommand = defineCommand11({
3314
4076
  meta: {
3315
4077
  name: "update",
3316
4078
  description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths."
@@ -3355,7 +4117,7 @@ var updateCommand = defineCommand10({
3355
4117
  });
3356
4118
 
3357
4119
  // src/index.ts
3358
- var main = defineCommand11({
4120
+ var main = defineCommand12({
3359
4121
  meta: {
3360
4122
  name: "agent-kit",
3361
4123
  description: "HITL framework for AI-assisted IDEs"
@@ -3365,6 +4127,7 @@ var main = defineCommand11({
3365
4127
  install: installCommand,
3366
4128
  scan: scanCommand,
3367
4129
  add: addCommand,
4130
+ doctor: doctorCommand,
3368
4131
  status: statusCommand,
3369
4132
  update: updateCommand,
3370
4133
  diff: diffCommand,