huddle60 0.1.0 → 0.1.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.
package/dist/cli.js CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  // src/entry/cli.ts
4
4
  import { execFileSync as execFileSync2 } from "node:child_process";
5
- import { readFile as readFile2 } from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ import { mkdir, readdir, readFile as readFile2, writeFile } from "node:fs/promises";
7
+ import { dirname, join as join2, relative } from "node:path";
8
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6
9
  import { parseArgs } from "node:util";
7
10
 
8
11
  // src/adapters/gemini.ts
@@ -129,8 +132,18 @@ import { retry } from "@octokit/plugin-retry";
129
132
  import { Octokit } from "@octokit/rest";
130
133
  var RetryingOctokit = Octokit.plugin(retry);
131
134
  var USER_AGENT = "huddle60/0.1.0";
135
+ var defaultLog = process.env.AI_DLC_DEBUG ? void 0 : {
136
+ debug: () => {
137
+ },
138
+ info: () => {
139
+ },
140
+ warn: () => {
141
+ },
142
+ error: () => {
143
+ }
144
+ };
132
145
  function octokitFromToken(token2) {
133
- return new RetryingOctokit({ auth: token2, userAgent: USER_AGENT });
146
+ return new RetryingOctokit({ auth: token2, userAgent: USER_AGENT, log: defaultLog });
134
147
  }
135
148
  function isStatus(error, ...statuses) {
136
149
  return typeof error === "object" && error !== null && statuses.includes(error.status ?? -1);
@@ -486,7 +499,7 @@ var GitBranchEvidenceStore = class {
486
499
  });
487
500
  const commit = await this.octokit.rest.git.createCommit({
488
501
  ...this.base,
489
- message: `ai-dlc: add ${path}`,
502
+ message: `huddle60: add ${path}`,
490
503
  tree: tree.data.sha,
491
504
  parents: [head.sha]
492
505
  });
@@ -549,7 +562,7 @@ var GitBranchEvidenceStore = class {
549
562
  });
550
563
  const commit = await this.octokit.rest.git.createCommit({
551
564
  ...this.base,
552
- message: "ai-dlc: initialize evidence branch",
565
+ message: "huddle60: initialize evidence branch",
553
566
  tree: tree.data.sha,
554
567
  parents: []
555
568
  });
@@ -659,7 +672,7 @@ var PolicySchema = z.object({
659
672
  strict: { requires_po_approval: true }
660
673
  }),
661
674
  sensitive_paths: z.array(z.string().min(1)).default([]),
662
- evidence: z.object({ branch: z.string().min(1).default("ai-dlc-evidence") }).default({ branch: "ai-dlc-evidence" }),
675
+ evidence: z.object({ branch: z.string().min(1).default("huddle60-evidence") }).default({ branch: "huddle60-evidence" }),
663
676
  acceptance: z.object({ build_workflows: z.array(z.string().min(1)).min(1) }).default({ build_workflows: [".github/workflows/build.yml"] }),
664
677
  planner: z.object({
665
678
  max_nodes: z.number().int().min(1).max(200).default(60),
@@ -673,7 +686,8 @@ var PolicySchema = z.object({
673
686
  ctx.addIssue({ code: "custom", message: `Bot account ${bot.login} cannot hold a role` });
674
687
  }
675
688
  });
676
- var POLICY_PATH = ".github/ai-dlc/policy.yml";
689
+ var POLICY_PATH = ".github/huddle60/policy.yml";
690
+ var LEGACY_POLICY_PATH = ".github/ai-dlc/policy.yml";
677
691
  function parsePolicy(source) {
678
692
  let raw;
679
693
  try {
@@ -918,13 +932,13 @@ function blockingQuestions(plan) {
918
932
 
919
933
  // src/core/vocabulary.ts
920
934
  var LABELS = {
921
- request: "ai-dlc:request",
922
- planDraft: "ai-dlc:plan-draft",
923
- planCreated: "ai-dlc:plan-created",
924
- planPartial: "ai-dlc:plan-partial",
925
- fromPlan: "ai-dlc:from-plan",
926
- stale: "ai-dlc:stale",
927
- exempt: "ai-dlc:exempt",
935
+ request: "huddle60:request",
936
+ planDraft: "huddle60:plan-draft",
937
+ planCreated: "huddle60:plan-created",
938
+ planPartial: "huddle60:plan-partial",
939
+ fromPlan: "huddle60:from-plan",
940
+ stale: "huddle60:stale",
941
+ exempt: "huddle60:exempt",
928
942
  epic: "type:epic",
929
943
  story: "type:story",
930
944
  task: "type:task"
@@ -987,13 +1001,13 @@ function kindFromLabels(labels) {
987
1001
 
988
1002
  // src/core/render.ts
989
1003
  function sanitize(text) {
990
- return text.replace(/<!--/gu, "&lt;!--").replace(/</gu, "&lt;").replace(/(^|[^\w`])@(?=[\w-])/gu, "$1@\u200B").replace(/^\/ai-dlc/gmu, "\\/ai-dlc");
1004
+ return text.replace(/<!--/gu, "&lt;!--").replace(/</gu, "&lt;").replace(/(^|[^\w`])@(?=[\w-])/gu, "$1@\u200B").replace(/^\/(?:huddle60|ai-dlc)/gmu, "\\/huddle60");
991
1005
  }
992
1006
  function marker(name, value) {
993
- return `<!-- ai-dlc:${name}:${value} -->`;
1007
+ return `<!-- huddle60:${name}:${value} -->`;
994
1008
  }
995
1009
  function findMarker(body, name) {
996
- const match = new RegExp(`<!-- ai-dlc:${name}:([^\\s>]+) -->`, "u").exec(body ?? "");
1010
+ const match = new RegExp(`<!-- (?:huddle60|ai-dlc):${name}:([^\\s>]+) -->`, "u").exec(body ?? "");
997
1011
  return match ? match[1] : null;
998
1012
  }
999
1013
  function planNodeMarker(planId, localId) {
@@ -1081,17 +1095,17 @@ function renderPlanComment(record) {
1081
1095
  "**C\u1EA7n PO tr\u1EA3 l\u1EDDi c\xE1c c\xE2u h\u1ECFi ch\u1EB7n tr\u01B0\u1EDBc khi t\u1EA1o Issues.** Tr\u1EA3 l\u1EDDi b\u1EB1ng m\u1ED9t comment m\u1EDBi:",
1082
1096
  "",
1083
1097
  "```text",
1084
- `/ai-dlc refine plan=${record.planId}`,
1098
+ `/huddle60 refine plan=${record.planId}`,
1085
1099
  "<c\xE2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n, m\u1ED7i \xFD m\u1ED9t d\xF2ng>",
1086
1100
  "```"
1087
1101
  ] : [
1088
1102
  "**N\u1EBFu \u0111\u1ED3ng \xFD, PO x\xE1c nh\u1EADn \u0111\u1EC3 App t\u1EA1o c\xE1c Issues:**",
1089
1103
  "",
1090
1104
  "```text",
1091
- `/ai-dlc create-plan plan=${record.planId} hash=${record.planHash}`,
1105
+ `/huddle60 create-plan plan=${record.planId} hash=${record.planHash}`,
1092
1106
  "```",
1093
1107
  "",
1094
- `Mu\u1ED1n ch\u1EC9nh s\u1EEDa: \`/ai-dlc refine plan=${record.planId}\` k\xE8m y\xEAu c\u1EA7u \u1EDF c\xE1c d\xF2ng ti\u1EBFp theo.`
1108
+ `Mu\u1ED1n ch\u1EC9nh s\u1EEDa: \`/huddle60 refine plan=${record.planId}\` k\xE8m y\xEAu c\u1EA7u \u1EDF c\xE1c d\xF2ng ti\u1EBFp theo.`
1095
1109
  ];
1096
1110
  return [
1097
1111
  `## K\u1EBF ho\u1EA1ch \u0111\u1EC1 xu\u1EA5t (revision ${record.revision})`,
@@ -1133,7 +1147,7 @@ function renderCreationSummary(record, numbers, complete, failures) {
1133
1147
  ...lines,
1134
1148
  "",
1135
1149
  ...failures.length > 0 ? ["**L\u1ED7i:**", "", bulletList(failures), ""] : [],
1136
- complete ? "B\u01B0\u1EDBc ti\u1EBFp theo: PO \u01B0u ti\xEAn trong Project; DEV review kh\u1EA3 thi v\xE0 chia vi\u1EC7c; ch\u1ED1t baseline khi Story s\u1EB5n s\xE0ng (`/ai-dlc baseline`)." : `Kh\xF4ng x\xF3a c\xE1c Issue \u0111\xE3 t\u1EA1o. Th\u1EED l\u1EA1i an to\xE0n b\u1EB1ng \`/ai-dlc resume-plan plan=${record.planId}\`.`,
1150
+ complete ? "B\u01B0\u1EDBc ti\u1EBFp theo: PO \u01B0u ti\xEAn trong Project; DEV review kh\u1EA3 thi v\xE0 chia vi\u1EC7c; ch\u1ED1t baseline khi Story s\u1EB5n s\xE0ng (`/huddle60 baseline`)." : `Kh\xF4ng x\xF3a c\xE1c Issue \u0111\xE3 t\u1EA1o. Th\u1EED l\u1EA1i an to\xE0n b\u1EB1ng \`/huddle60 resume-plan plan=${record.planId}\`.`,
1137
1151
  marker("plan-result", record.planId)
1138
1152
  ].join("\n");
1139
1153
  }
@@ -1514,45 +1528,59 @@ function baselinePath(repositoryId, issueNumber, baselineId) {
1514
1528
  }
1515
1529
 
1516
1530
  // src/core/pr-block.ts
1517
- var PR_BLOCK_BEGIN = "<!-- ai-dlc:begin -->";
1518
- var PR_BLOCK_END = "<!-- ai-dlc:end -->";
1531
+ var PR_BLOCK_BEGIN = "<!-- huddle60:begin -->";
1532
+ var PR_BLOCK_END = "<!-- huddle60:end -->";
1533
+ var LEGACY_PR_BLOCK_BEGIN = "<!-- ai-dlc:begin -->";
1534
+ var LEGACY_PR_BLOCK_END = "<!-- ai-dlc:end -->";
1519
1535
  function parsePrBlock(body) {
1520
1536
  const text = (body ?? "").replace(/\r\n?/gu, "\n");
1521
- const begin = text.indexOf(PR_BLOCK_BEGIN);
1537
+ let begin = text.indexOf(PR_BLOCK_BEGIN);
1538
+ let beginMarker = PR_BLOCK_BEGIN;
1539
+ let endMarker = PR_BLOCK_END;
1540
+ if (begin === -1) {
1541
+ begin = text.indexOf(LEGACY_PR_BLOCK_BEGIN);
1542
+ if (begin !== -1) {
1543
+ beginMarker = LEGACY_PR_BLOCK_BEGIN;
1544
+ endMarker = LEGACY_PR_BLOCK_END;
1545
+ }
1546
+ }
1522
1547
  if (begin === -1) return null;
1523
- if (text.indexOf(PR_BLOCK_BEGIN, begin + 1) !== -1) {
1524
- throw new AiDlcError("E_PR_BLOCK", "PR body contains more than one AI-DLC block.");
1548
+ if (text.indexOf(beginMarker, begin + 1) !== -1) {
1549
+ throw new AiDlcError("E_PR_BLOCK", "PR body contains more than one huddle60 block.");
1525
1550
  }
1526
- const end = text.indexOf(PR_BLOCK_END, begin);
1527
- if (end === -1) throw new AiDlcError("E_PR_BLOCK", "AI-DLC block is not closed.");
1551
+ const end = text.indexOf(endMarker, begin);
1552
+ if (end === -1) throw new AiDlcError("E_PR_BLOCK", "huddle60 block is not closed.");
1528
1553
  const fields = /* @__PURE__ */ new Map();
1529
- for (const line of text.slice(begin + PR_BLOCK_BEGIN.length, end).split("\n")) {
1530
- const match = /^AI-DLC-([A-Za-z]+):\s*(.*)$/u.exec(line.trim());
1554
+ for (const line of text.slice(begin + beginMarker.length, end).split("\n")) {
1555
+ const match = /^(?:HUDDLE60|AI-DLC)-([A-Za-z]+):\s*(.*)$/iu.exec(line.trim());
1531
1556
  if (match) fields.set(match[1].toLowerCase(), match[2].trim());
1532
1557
  }
1533
1558
  const issue = /^#(\d+)$/u.exec(fields.get("issue") ?? "");
1534
1559
  const baselineId = fields.get("baseline") ?? "";
1535
1560
  const hash = fields.get("hash") ?? "";
1536
1561
  if (!issue)
1537
- throw new AiDlcError("E_PR_BLOCK", "AI-DLC-Issue must be `#<number>` in the same repository.");
1562
+ throw new AiDlcError(
1563
+ "E_PR_BLOCK",
1564
+ "HUDDLE60-Issue must be `#<number>` in the same repository."
1565
+ );
1538
1566
  if (!/^bl-\d+-[0-9a-f]{12}$/u.test(baselineId))
1539
- throw new AiDlcError("E_PR_BLOCK", "AI-DLC-Baseline is missing or invalid.");
1567
+ throw new AiDlcError("E_PR_BLOCK", "HUDDLE60-Baseline is missing or invalid.");
1540
1568
  if (!/^sha256:[0-9a-f]{64}$/u.test(hash))
1541
- throw new AiDlcError("E_PR_BLOCK", "AI-DLC-Hash is missing or invalid.");
1569
+ throw new AiDlcError("E_PR_BLOCK", "HUDDLE60-Hash is missing or invalid.");
1542
1570
  const acceptanceCriteria = (fields.get("ac") ?? "").split(/[\s,]+/u).filter((value) => value.length > 0);
1543
1571
  for (const id of acceptanceCriteria) {
1544
1572
  if (!/^AC-\d{2,3}$/u.test(id))
1545
- throw new AiDlcError("E_PR_BLOCK", `Invalid AC id \`${id}\` in AI-DLC-AC.`);
1573
+ throw new AiDlcError("E_PR_BLOCK", `Invalid AC id \`${id}\` in HUDDLE60-AC.`);
1546
1574
  }
1547
1575
  return { issueNumber: Number(issue[1]), baselineId, hash, acceptanceCriteria };
1548
1576
  }
1549
1577
  function renderPrBlock(block) {
1550
1578
  return [
1551
1579
  PR_BLOCK_BEGIN,
1552
- `AI-DLC-Issue: #${block.issueNumber}`,
1553
- `AI-DLC-Baseline: ${block.baselineId}`,
1554
- `AI-DLC-Hash: ${block.hash}`,
1555
- `AI-DLC-AC: ${block.acceptanceCriteria.join(", ")}`,
1580
+ `HUDDLE60-Issue: #${block.issueNumber}`,
1581
+ `HUDDLE60-Baseline: ${block.baselineId}`,
1582
+ `HUDDLE60-Hash: ${block.hash}`,
1583
+ `HUDDLE60-AC: ${block.acceptanceCriteria.join(", ")}`,
1556
1584
  PR_BLOCK_END
1557
1585
  ].join("\n");
1558
1586
  }
@@ -1587,7 +1615,7 @@ async function renderAgentContext(deps, policy, issueNumber, draft = false) {
1587
1615
 
1588
1616
  ${value}`);
1589
1617
  return [
1590
- `# AI-DLC context for #${issueNumber}`,
1618
+ `# huddle60 context for #${issueNumber}`,
1591
1619
  "",
1592
1620
  `Baseline \`${snapshot.baselineId}\` \xB7 lane \`${receipt.lane}\` \xB7 ${snapshot.kind}`,
1593
1621
  ...stale ? [
@@ -1595,7 +1623,7 @@ ${value}`);
1595
1623
  "> WARNING: the Issue changed after this baseline. Stop and ask the DEV/PO before implementing."
1596
1624
  ] : [],
1597
1625
  "",
1598
- "Implement only what this baseline describes. Do not run /ai-dlc approve, ready or accept.",
1626
+ "Implement only what this baseline describes. Do not run /huddle60 approve, ready or accept.",
1599
1627
  "",
1600
1628
  `## ${snapshot.requirements.title}`,
1601
1629
  "",
@@ -1615,12 +1643,13 @@ ${value}`);
1615
1643
  }
1616
1644
  throw new AiDlcError(
1617
1645
  "E_NOT_READY",
1618
- `#${issueNumber} has no valid ready decision. Ask a DEV to run /ai-dlc ready, or use --draft.`
1646
+ `#${issueNumber} has no valid ready decision. Ask a DEV to run /huddle60 ready, or use --draft.`
1619
1647
  );
1620
1648
  }
1621
1649
 
1622
1650
  // src/core/command.ts
1623
- var COMMAND_PREFIX = "/ai-dlc";
1651
+ var COMMAND_PREFIX = "/huddle60";
1652
+ var LEGACY_COMMAND_PREFIX = "/ai-dlc";
1624
1653
  var COMMAND_SPECS = {
1625
1654
  baseline: { required: [], optional: [], handler: "deterministic" },
1626
1655
  approve: { required: ["baseline", "hash"], optional: [], handler: "deterministic" },
@@ -1650,13 +1679,18 @@ function parseCommand(body) {
1650
1679
  const normalized = body.replace(/\r\n?/gu, "\n");
1651
1680
  const [head = "", ...rest] = normalized.split("\n");
1652
1681
  const line = head.trim();
1653
- if (!line.startsWith(COMMAND_PREFIX)) {
1682
+ let prefix = "";
1683
+ if (line.startsWith(COMMAND_PREFIX)) {
1684
+ prefix = COMMAND_PREFIX;
1685
+ } else if (line.startsWith(LEGACY_COMMAND_PREFIX)) {
1686
+ prefix = LEGACY_COMMAND_PREFIX;
1687
+ } else {
1654
1688
  throw new AiDlcError(
1655
1689
  "E_SYNTAX",
1656
1690
  `Commands must start with \`${COMMAND_PREFIX}\` on the first line.`
1657
1691
  );
1658
1692
  }
1659
- const remainder = line.slice(COMMAND_PREFIX.length).trim();
1693
+ const remainder = line.slice(prefix.length).trim();
1660
1694
  const [rawName = "", ...argParts] = remainder.split(/\s+/u);
1661
1695
  if (!(rawName in COMMAND_SPECS)) {
1662
1696
  throw new AiDlcError(
@@ -1686,7 +1720,7 @@ function parseCommand(body) {
1686
1720
  }
1687
1721
 
1688
1722
  // src/workflow/service.ts
1689
- var STATUS_CONTEXT = "ai-dlc/baseline";
1723
+ var STATUS_CONTEXT = "huddle60/baseline";
1690
1724
  var LANES = ["fast", "standard", "strict"];
1691
1725
  var WorkflowService = class {
1692
1726
  constructor(deps) {
@@ -1700,7 +1734,10 @@ var WorkflowService = class {
1700
1734
  async policy() {
1701
1735
  if (this.policyCache) return this.policyCache;
1702
1736
  const branch = await this.deps.github.getDefaultBranch();
1703
- const source = await this.deps.github.readFile(POLICY_PATH, branch);
1737
+ let source = await this.deps.github.readFile(POLICY_PATH, branch);
1738
+ if (source === null) {
1739
+ source = await this.deps.github.readFile(LEGACY_POLICY_PATH, branch);
1740
+ }
1704
1741
  if (source === null)
1705
1742
  throw new AiDlcError("E_POLICY_INVALID", `Missing ${POLICY_PATH} on ${branch}.`);
1706
1743
  this.policyCache = parsePolicy(source);
@@ -1790,11 +1827,11 @@ var WorkflowService = class {
1790
1827
  "",
1791
1828
  "PO duy\u1EC7t nghi\u1EC7p v\u1EE5:",
1792
1829
  "```text",
1793
- `/ai-dlc approve baseline=${snapshot.baselineId} hash=${snapshot.contentHash}`,
1830
+ `/huddle60 approve baseline=${snapshot.baselineId} hash=${snapshot.contentHash}`,
1794
1831
  "```",
1795
1832
  "DEV x\xE1c nh\u1EADn s\u1EB5n s\xE0ng (lane: fast | standard | strict):",
1796
1833
  "```text",
1797
- `/ai-dlc ready baseline=${snapshot.baselineId} hash=${snapshot.contentHash} lane=standard`,
1834
+ `/huddle60 ready baseline=${snapshot.baselineId} hash=${snapshot.contentHash} lane=standard`,
1798
1835
  "```"
1799
1836
  ].join("\n");
1800
1837
  }
@@ -1840,7 +1877,7 @@ var WorkflowService = class {
1840
1877
  if (this.snapshotOf(issue).contentHash !== snapshot.contentHash) {
1841
1878
  throw new AiDlcError(
1842
1879
  "E_STALE",
1843
- "The Issue changed after this baseline was created. Run `/ai-dlc baseline` again and decide on the new baseline."
1880
+ "The Issue changed after this baseline was created. Run `/huddle60 baseline` again and decide on the new baseline."
1844
1881
  );
1845
1882
  }
1846
1883
  }
@@ -2065,7 +2102,7 @@ var WorkflowService = class {
2065
2102
  };
2066
2103
  }) : {
2067
2104
  state: "pending",
2068
- description: "AI-DLC is disabled by the operator",
2105
+ description: "huddle60 is disabled by the operator",
2069
2106
  details: []
2070
2107
  };
2071
2108
  await this.deps.github.setCommitStatus(pr.headSha, {
@@ -2096,12 +2133,12 @@ var WorkflowService = class {
2096
2133
  if (!block)
2097
2134
  throw new AiDlcError(
2098
2135
  "E_PR_BLOCK",
2099
- "PR body has no AI-DLC block. Run the context CLI or use the ready reply."
2136
+ "PR body has no huddle60 block. Run the context CLI or use the ready reply."
2100
2137
  );
2101
2138
  const issue = await github.getIssue(block.issueNumber);
2102
2139
  const snapshot = await this.loadSnapshot(issue.number, block.baselineId);
2103
2140
  if (snapshot.contentHash !== block.hash)
2104
- throw new AiDlcError("E_HASH_MISMATCH", "AI-DLC-Hash does not match the baseline.");
2141
+ throw new AiDlcError("E_HASH_MISMATCH", "HUDDLE60-Hash does not match the baseline.");
2105
2142
  if (snapshot.kind === "epic")
2106
2143
  throw new AiDlcError("E_PR_BLOCK", "A PR must reference a story or task, not an epic.");
2107
2144
  this.assertCurrent(issue, snapshot);
@@ -2182,7 +2219,7 @@ var WorkflowService = class {
2182
2219
  }
2183
2220
  };
2184
2221
  function errorReply(error) {
2185
- return [`**AI-DLC: \`${error.code}\`**`, "", sanitize(error.message)].join("\n");
2222
+ return [`**huddle60: \`${error.code}\`**`, "", sanitize(error.message)].join("\n");
2186
2223
  }
2187
2224
  function firstLine(text) {
2188
2225
  return text.split("\n")[0] ?? "";
@@ -2195,13 +2232,13 @@ function truncate(text, max) {
2195
2232
  var LANES2 = ["fast", "standard", "strict"];
2196
2233
  var MAX_DRAFTS = 50;
2197
2234
  var LABEL_COLORS = {
2198
- [LABELS.request]: { color: "5319e7", description: "AI-DLC: product request from a PO" },
2199
- [LABELS.planDraft]: { color: "c5def5", description: "AI-DLC: plan proposed, not created" },
2200
- [LABELS.planCreated]: { color: "0e8a16", description: "AI-DLC: Issues created from the plan" },
2201
- [LABELS.planPartial]: { color: "d93f0b", description: "AI-DLC: Issue creation incomplete" },
2202
- [LABELS.fromPlan]: { color: "bfdadc", description: "AI-DLC: created from a confirmed plan" },
2203
- [LABELS.stale]: { color: "fbca04", description: "AI-DLC: content changed after decisions" },
2204
- [LABELS.exempt]: { color: "ededed", description: "AI-DLC: PR exempt (maintainer only)" },
2235
+ [LABELS.request]: { color: "5319e7", description: "huddle60: product request from a PO" },
2236
+ [LABELS.planDraft]: { color: "c5def5", description: "huddle60: plan proposed, not created" },
2237
+ [LABELS.planCreated]: { color: "0e8a16", description: "huddle60: Issues created from the plan" },
2238
+ [LABELS.planPartial]: { color: "d93f0b", description: "huddle60: Issue creation incomplete" },
2239
+ [LABELS.fromPlan]: { color: "bfdadc", description: "huddle60: created from a confirmed plan" },
2240
+ [LABELS.stale]: { color: "fbca04", description: "huddle60: content changed after decisions" },
2241
+ [LABELS.exempt]: { color: "ededed", description: "huddle60: PR exempt (maintainer only)" },
2205
2242
  [LABELS.epic]: { color: "3e4b9e", description: "Epic" },
2206
2243
  [LABELS.story]: { color: "1d76db", description: "User story" },
2207
2244
  [LABELS.task]: { color: "0052cc", description: "Technical task" }
@@ -2501,13 +2538,13 @@ ${sanitize(draft.request)}
2501
2538
  let command;
2502
2539
  switch (input.action) {
2503
2540
  case "approve":
2504
- command = `/ai-dlc approve ${target} hash=${snapshot.contentHash}`;
2541
+ command = `/huddle60 approve ${target} hash=${snapshot.contentHash}`;
2505
2542
  break;
2506
2543
  case "ready": {
2507
2544
  const lane = input.lane;
2508
2545
  if (!LANES2.includes(lane))
2509
2546
  throw new AiDlcError("E_SYNTAX", `Lane must be one of ${LANES2.join(", ")}.`);
2510
- command = `/ai-dlc ready ${target} hash=${snapshot.contentHash} lane=${lane}`;
2547
+ command = `/huddle60 ready ${target} hash=${snapshot.contentHash} lane=${lane}`;
2511
2548
  break;
2512
2549
  }
2513
2550
  case "accept": {
@@ -2519,7 +2556,7 @@ ${sanitize(draft.request)}
2519
2556
  "No successful acceptance build on the default branch. Merge the PR and wait for the build."
2520
2557
  );
2521
2558
  }
2522
- command = `/ai-dlc accept ${target} run=${build.id} commit=${build.headSha}`;
2559
+ command = `/huddle60 accept ${target} run=${build.id} commit=${build.headSha}`;
2523
2560
  break;
2524
2561
  }
2525
2562
  default:
@@ -2535,7 +2572,11 @@ ${sanitize(draft.request)}
2535
2572
  );
2536
2573
  if (!found) return { status: "pending", body: null, ok: false };
2537
2574
  const body = found.body.replace(/<!--[\s\S]*?-->/gu, "").trim();
2538
- return { status: "done", body, ok: !body.startsWith("**AI-DLC: `E_") };
2575
+ return {
2576
+ status: "done",
2577
+ body,
2578
+ ok: !body.startsWith("**huddle60: `E_") && !body.startsWith("**AI-DLC: `E_")
2579
+ };
2539
2580
  }
2540
2581
  async builds() {
2541
2582
  const policy = await this.workflow.policy();
@@ -2582,19 +2623,15 @@ import { fileURLToPath } from "node:url";
2582
2623
  var systemClock = { now: () => /* @__PURE__ */ new Date() };
2583
2624
 
2584
2625
  // src/app/server.ts
2626
+ import { timingSafeEqual } from "node:crypto";
2585
2627
  import { readFile } from "node:fs/promises";
2586
2628
  import { join } from "node:path";
2587
2629
  import { z as z3 } from "zod";
2588
-
2589
- // src/server/http.ts
2590
- import { createHmac, timingSafeEqual } from "node:crypto";
2591
2630
  function safeEqual(a, b) {
2592
2631
  const left = Buffer.from(a);
2593
2632
  const right = Buffer.from(b);
2594
2633
  return left.length === right.length && timingSafeEqual(left, right);
2595
2634
  }
2596
-
2597
- // src/app/server.ts
2598
2635
  var TOKEN_HEADER = "x-ai-dlc-token";
2599
2636
  var MAX_BODY = 256 * 1024;
2600
2637
  var STATIC_FILES = {
@@ -2792,7 +2829,10 @@ async function startLocalApp(options) {
2792
2829
  const logger = createJsonLogger({ component: "huddle60-ui" });
2793
2830
  const octokit = octokitFromToken(resolveGitHubToken());
2794
2831
  const github = await connectRepository(octokit, options.owner, options.repo);
2795
- const policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2832
+ let policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2833
+ if (!policySource) {
2834
+ policySource = await github.readFile(LEGACY_POLICY_PATH, await github.getDefaultBranch());
2835
+ }
2796
2836
  if (!policySource)
2797
2837
  throw new AiDlcError(
2798
2838
  "E_POLICY_INVALID",
@@ -2889,7 +2929,10 @@ async function context(repoArg, issueArg, draft) {
2889
2929
  const [owner, repo] = detectRepo(repoArg);
2890
2930
  const octokit = octokitFromToken(token());
2891
2931
  const github = await connectRepository(octokit, owner, repo);
2892
- const policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2932
+ let policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2933
+ if (!policySource) {
2934
+ policySource = await github.readFile(LEGACY_POLICY_PATH, await github.getDefaultBranch());
2935
+ }
2893
2936
  if (!policySource) throw new Error(`Missing ${POLICY_PATH} in ${owner}/${repo}.`);
2894
2937
  const policy = parsePolicy(policySource);
2895
2938
  const evidence = new GitBranchEvidenceStore(octokit, owner, repo, policy.evidence.branch);
@@ -2902,31 +2945,128 @@ async function context(repoArg, issueArg, draft) {
2902
2945
  });
2903
2946
  return renderAgentContext({ github, evidence, workflow }, policy, issueNumber, draft);
2904
2947
  }
2948
+ function resolveTemplatesDir() {
2949
+ const distRelative = fileURLToPath2(new URL("../templates", import.meta.url));
2950
+ if (existsSync(distRelative)) return distRelative;
2951
+ const srcRelative = fileURLToPath2(new URL("../../templates", import.meta.url));
2952
+ if (existsSync(srcRelative)) return srcRelative;
2953
+ throw new Error("Cannot find templates directory.");
2954
+ }
2955
+ async function scaffoldTemplates(destDir, viewer) {
2956
+ const templatesDir = resolveTemplatesDir();
2957
+ const created = [];
2958
+ const skipped = [];
2959
+ async function walk(currentSrc, currentDest) {
2960
+ const entries = await readdir(currentSrc, { withFileTypes: true });
2961
+ for (const entry of entries) {
2962
+ const srcPath = join2(currentSrc, entry.name);
2963
+ const destPath = join2(currentDest, entry.name);
2964
+ if (entry.isDirectory()) {
2965
+ await walk(srcPath, destPath);
2966
+ } else if (entry.isFile()) {
2967
+ const rel = relative(destDir, destPath);
2968
+ if (existsSync(destPath)) {
2969
+ skipped.push(rel);
2970
+ continue;
2971
+ }
2972
+ await mkdir(dirname(destPath), { recursive: true });
2973
+ let content = await readFile2(srcPath, "utf8");
2974
+ if (entry.name === "policy.yml") {
2975
+ content = content.replace(
2976
+ /roles:[\s\S]*?maintainer:\s*\[\]/u,
2977
+ `roles:
2978
+ po:
2979
+ - id: ${viewer.id}
2980
+ login: ${viewer.login}
2981
+ dev:
2982
+ - id: ${viewer.id}
2983
+ login: ${viewer.login}
2984
+ maintainer:
2985
+ - id: ${viewer.id}
2986
+ login: ${viewer.login}`
2987
+ ).replace(
2988
+ /project:\s*\n\s*owner:\s*your-org\s*\n\s*number:\s*1/u,
2989
+ `# project:
2990
+ # owner: your-org
2991
+ # number: 1`
2992
+ );
2993
+ } else if (entry.name === "huddle60.yml" || entry.name === "ai-dlc.yml") {
2994
+ content = content.replace("your-org/huddle60@v0", "tadev999/huddle60@main");
2995
+ }
2996
+ await writeFile(destPath, content, "utf8");
2997
+ created.push(rel);
2998
+ }
2999
+ }
3000
+ }
3001
+ const githubSrc = join2(templatesDir, ".github");
3002
+ if (existsSync(githubSrc)) {
3003
+ await walk(githubSrc, join2(destDir, ".github"));
3004
+ }
3005
+ const agentsSrc = join2(templatesDir, "AGENTS.md");
3006
+ const agentsDest = join2(destDir, "AGENTS.md");
3007
+ if (existsSync(agentsSrc)) {
3008
+ if (existsSync(agentsDest)) {
3009
+ skipped.push("AGENTS.md");
3010
+ } else {
3011
+ const content = await readFile2(agentsSrc, "utf8");
3012
+ await writeFile(agentsDest, content, "utf8");
3013
+ created.push("AGENTS.md");
3014
+ }
3015
+ }
3016
+ return { created, skipped };
3017
+ }
2905
3018
  async function initEvidence(repoArg) {
2906
3019
  const [owner, repo] = detectRepo(repoArg);
2907
3020
  const octokit = octokitFromToken(token());
2908
3021
  const github = await connectRepository(octokit, owner, repo);
2909
- const policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2910
- const branch = policySource ? parsePolicy(policySource).evidence.branch : "ai-dlc-evidence";
3022
+ let policySource = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
3023
+ if (!policySource) {
3024
+ policySource = await github.readFile(LEGACY_POLICY_PATH, await github.getDefaultBranch());
3025
+ }
3026
+ const branch = policySource ? parsePolicy(policySource).evidence.branch : "huddle60-evidence";
2911
3027
  const result = await new GitBranchEvidenceStore(octokit, owner, repo, branch).initialize();
2912
- return result === "created" ? `Created branch ${branch}. Protect it with a ruleset that blocks force-push and deletion.` : `Branch ${branch} already exists.`;
3028
+ return result === "created" ? `\u2713 Created branch ${branch}. Protect it with a ruleset that blocks force-push and deletion.` : `\u2713 Branch ${branch} already exists.`;
2913
3029
  }
2914
3030
  async function init(repoArg) {
2915
3031
  const [owner, repo] = detectRepo(repoArg);
2916
3032
  const github = await connectRepository(octokitFromToken(token()), owner, repo);
2917
- const created = [];
3033
+ const viewer = await github.viewer();
3034
+ const createdLabels = [];
2918
3035
  for (const [name, meta] of Object.entries(LABEL_COLORS)) {
2919
- if (await github.ensureLabel({ name, ...meta }) === "created") created.push(name);
3036
+ if (await github.ensureLabel({ name, ...meta }) === "created") createdLabels.push(name);
2920
3037
  }
2921
3038
  const lines = [
2922
- created.length > 0 ? `Created labels: ${created.join(", ")}` : "Labels already exist.",
3039
+ `\u2713 Authenticated as ${viewer.login} (id: ${viewer.id}).`,
3040
+ createdLabels.length > 0 ? `\u2713 Created labels: ${createdLabels.join(", ")}` : "\u2713 Labels already exist.",
2923
3041
  await initEvidence(repoArg)
2924
3042
  ];
2925
- const policy = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
2926
- if (!policy)
3043
+ try {
3044
+ const { created, skipped } = await scaffoldTemplates(process.cwd(), viewer);
3045
+ if (created.length > 0) {
3046
+ lines.push(
3047
+ `\u2713 Scaffolding completed (${created.length} files created):`,
3048
+ ...created.map((f) => ` + ${f}`)
3049
+ );
3050
+ }
3051
+ if (skipped.length > 0) {
3052
+ lines.push(`\u2139 Kept existing files (${skipped.length}): ${skipped.join(", ")}`);
3053
+ }
2927
3054
  lines.push(
2928
- `Next: commit templates/.github/** (policy with your user ids from \`gh api user --jq .id\`) to the default branch.`
3055
+ "",
3056
+ `Setup complete! Next steps:`,
3057
+ ` 1. Commit and push templates to default branch:`,
3058
+ ` git add .github AGENTS.md && git commit -m "chore: setup huddle60" && git push`,
3059
+ ` 2. Start the local app:`,
3060
+ ` npx huddle60 ui`
2929
3061
  );
3062
+ } catch {
3063
+ const policy = await github.readFile(POLICY_PATH, await github.getDefaultBranch());
3064
+ if (!policy) {
3065
+ lines.push(
3066
+ `Next: commit templates/.github/** (policy with user id ${viewer.id}) to the default branch.`
3067
+ );
3068
+ }
3069
+ }
2930
3070
  return lines.join("\n");
2931
3071
  }
2932
3072
  async function ui(repoArg, portArg, open) {
@@ -2942,7 +3082,7 @@ async function validatePolicy(file) {
2942
3082
  }
2943
3083
  async function planPreview(request) {
2944
3084
  if (!request)
2945
- throw new Error('Pass the request text, e.g. ai-dlc plan-preview "Login with Firebase".');
3085
+ throw new Error('Pass the request text, e.g. huddle60 plan-preview "Login with Google".');
2946
3086
  const apiKey = process.env.GEMINI_API_KEY;
2947
3087
  if (!apiKey) throw new Error("Set GEMINI_API_KEY to call Gemini.");
2948
3088
  const model = new GeminiModel({