agconf 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3521 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CURRENT_CONFIG_VERSION,
4
+ CanonicalRepoConfigSchema,
5
+ addManagedMetadata,
6
+ buildAgentsMd,
7
+ checkAllManagedFiles,
8
+ computeContentHash,
9
+ computeGlobalBlockHash,
10
+ computeRulesSectionHash,
11
+ extractRepoBlockContent,
12
+ getModifiedManagedFiles,
13
+ parseAgentsMd,
14
+ parseFrontmatter,
15
+ parseGlobalBlockMetadata,
16
+ parseRulesSection,
17
+ parseRulesSectionMetadata,
18
+ stripMetadataComments,
19
+ stripRulesSectionMetadata,
20
+ validateSkillFrontmatter
21
+ } from "./chunk-B53WKCQU.js";
22
+
23
+ // src/cli.ts
24
+ import { Command } from "commander";
25
+ import pc11 from "picocolors";
26
+
27
+ // src/commands/canonical.ts
28
+ import * as fs3 from "fs/promises";
29
+ import * as path3 from "path";
30
+ import * as prompts from "@clack/prompts";
31
+ import pc2 from "picocolors";
32
+ import { stringify as stringifyYaml } from "yaml";
33
+
34
+ // src/utils/fs.ts
35
+ import * as fs from "fs/promises";
36
+ import * as os from "os";
37
+ import * as path from "path";
38
+ async function ensureDir(dirPath) {
39
+ await fs.mkdir(dirPath, { recursive: true });
40
+ }
41
+ async function fileExists(filePath) {
42
+ try {
43
+ await fs.access(filePath);
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ async function directoryExists(dirPath) {
50
+ try {
51
+ const stat4 = await fs.stat(dirPath);
52
+ return stat4.isDirectory();
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ async function createTempDir(prefix = "agent-conf-") {
58
+ const tmpDir = os.tmpdir();
59
+ return fs.mkdtemp(path.join(tmpDir, prefix));
60
+ }
61
+ async function removeTempDir(dirPath) {
62
+ try {
63
+ await fs.rm(dirPath, { recursive: true, force: true });
64
+ } catch {
65
+ }
66
+ }
67
+ function resolvePath(inputPath) {
68
+ if (inputPath.startsWith("~")) {
69
+ return path.join(os.homedir(), inputPath.slice(1));
70
+ }
71
+ return path.resolve(inputPath);
72
+ }
73
+
74
+ // src/utils/git.ts
75
+ import * as fs2 from "fs/promises";
76
+ import * as path2 from "path";
77
+ import { simpleGit } from "simple-git";
78
+ async function directoryExistsForGit(dir) {
79
+ try {
80
+ const stat4 = await fs2.stat(dir);
81
+ return stat4.isDirectory();
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ async function getGitRoot(dir) {
87
+ if (!await directoryExistsForGit(dir)) {
88
+ return null;
89
+ }
90
+ try {
91
+ const git = simpleGit(dir);
92
+ const isRepo = await git.checkIsRepo();
93
+ if (!isRepo) {
94
+ return null;
95
+ }
96
+ const root = await git.revparse(["--show-toplevel"]);
97
+ return root.trim();
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ async function getGitProjectName(dir) {
103
+ const gitRoot = await getGitRoot(dir);
104
+ if (!gitRoot) {
105
+ return null;
106
+ }
107
+ return path2.basename(gitRoot);
108
+ }
109
+ async function isGitRoot(dir) {
110
+ if (!await directoryExistsForGit(dir)) {
111
+ return false;
112
+ }
113
+ const gitRoot = await getGitRoot(dir);
114
+ if (!gitRoot) {
115
+ return false;
116
+ }
117
+ try {
118
+ const realDir = await fs2.realpath(dir);
119
+ const realGitRoot = await fs2.realpath(gitRoot);
120
+ return realDir === realGitRoot;
121
+ } catch {
122
+ return path2.resolve(dir) === path2.resolve(gitRoot);
123
+ }
124
+ }
125
+ async function getGitOrganization(dir) {
126
+ if (!await directoryExistsForGit(dir)) {
127
+ return void 0;
128
+ }
129
+ try {
130
+ const git = simpleGit(dir);
131
+ const isRepo = await git.checkIsRepo();
132
+ if (!isRepo) {
133
+ return void 0;
134
+ }
135
+ const remotes = await git.getRemotes(true);
136
+ const origin = remotes.find((r) => r.name === "origin");
137
+ if (origin?.refs.fetch) {
138
+ const url = origin.refs.fetch;
139
+ const httpsMatch = url.match(/github\.com\/([^/]+)\//);
140
+ const sshMatch = url.match(/github\.com:([^/]+)\//);
141
+ const org = httpsMatch?.[1] ?? sshMatch?.[1];
142
+ if (org) {
143
+ return org;
144
+ }
145
+ }
146
+ const userName = await git.getConfig("user.name");
147
+ return userName.value ?? void 0;
148
+ } catch {
149
+ return void 0;
150
+ }
151
+ }
152
+
153
+ // src/utils/logger.ts
154
+ import ora from "ora";
155
+ import pc from "picocolors";
156
+ function createLogger(quiet = false) {
157
+ return {
158
+ info(message) {
159
+ if (!quiet) {
160
+ console.log(`${pc.blue("info")} ${message}`);
161
+ }
162
+ },
163
+ success(message) {
164
+ if (!quiet) {
165
+ console.log(`${pc.green("success")} ${message}`);
166
+ }
167
+ },
168
+ warn(message) {
169
+ console.log(`${pc.yellow("warn")} ${message}`);
170
+ },
171
+ error(message) {
172
+ console.error(`${pc.red("error")} ${message}`);
173
+ },
174
+ dim(message) {
175
+ if (!quiet) {
176
+ console.log(pc.dim(message));
177
+ }
178
+ },
179
+ spinner(text2) {
180
+ if (quiet) {
181
+ return ora({ text: text2, isSilent: true });
182
+ }
183
+ return ora({ text: text2, color: "blue" });
184
+ }
185
+ };
186
+ }
187
+ function formatPath(p, cwd = process.cwd()) {
188
+ if (p.startsWith(cwd)) {
189
+ return `.${p.slice(cwd.length)}`;
190
+ }
191
+ return p;
192
+ }
193
+
194
+ // src/commands/canonical.ts
195
+ function generateConfigYaml(options) {
196
+ const content = {
197
+ instructions: "instructions/AGENTS.md",
198
+ skills_dir: "skills"
199
+ };
200
+ if (options.rulesDir) {
201
+ content.rules_dir = options.rulesDir;
202
+ }
203
+ const config = {
204
+ version: CURRENT_CONFIG_VERSION,
205
+ meta: {
206
+ name: options.name
207
+ },
208
+ content,
209
+ targets: ["claude"],
210
+ markers: {
211
+ prefix: options.markerPrefix
212
+ },
213
+ merge: {
214
+ preserve_repo_content: true
215
+ }
216
+ };
217
+ if (options.organization) {
218
+ config.meta.organization = options.organization;
219
+ }
220
+ let yaml = stringifyYaml(config, { lineWidth: 0 });
221
+ if (!options.rulesDir) {
222
+ yaml = yaml.replace(/skills_dir: skills\n/, "skills_dir: skills\n # rules_dir: rules\n");
223
+ }
224
+ return yaml;
225
+ }
226
+ function generateAgentsMd(options) {
227
+ const orgName = options.organization ?? "Your Organization";
228
+ return `# ${orgName} Engineering Standards for AI Agents
229
+
230
+ This document defines company-wide engineering standards that all AI coding agents must follow.
231
+
232
+ ## Purpose
233
+
234
+ These standards ensure consistency, maintainability, and operational excellence across all engineering projects.
235
+
236
+ ---
237
+
238
+ ## Development Principles
239
+
240
+ ### Code Quality
241
+
242
+ - Write clean, readable code with meaningful names
243
+ - Follow existing patterns in the codebase
244
+ - Keep functions small and focused
245
+ - Add comments only when the "why" isn't obvious
246
+
247
+ ### Testing
248
+
249
+ - Write tests for new functionality
250
+ - Ensure tests are deterministic and fast
251
+ - Use descriptive test names
252
+
253
+ ---
254
+
255
+ ## Getting Started
256
+
257
+ Add your organization's specific engineering standards below.
258
+
259
+ ---
260
+
261
+ **Version**: 1.0
262
+ **Last Updated**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
263
+ `;
264
+ }
265
+ function generateExampleSkillMd() {
266
+ return `---
267
+ name: example-skill
268
+ description: An example skill demonstrating the skill format
269
+ ---
270
+
271
+ # Example Skill
272
+
273
+ This is an example skill that demonstrates the skill format.
274
+
275
+ ## When to Use
276
+
277
+ Use this skill when you need an example of how skills are structured.
278
+
279
+ ## Instructions
280
+
281
+ 1. Skills are defined in their own directories under \`skills/\`
282
+ 2. Each skill has a \`SKILL.md\` file with frontmatter
283
+ 3. The frontmatter must include \`name\` and \`description\`
284
+ 4. Optional: Include a \`references/\` directory for additional files
285
+
286
+ ## Example
287
+
288
+ \`\`\`
289
+ skills/
290
+ example-skill/
291
+ SKILL.md
292
+ references/
293
+ .gitkeep
294
+ \`\`\`
295
+ `;
296
+ }
297
+ function generateSyncWorkflow(repoFullName, prefix) {
298
+ return `# ${prefix} Auto-Sync Workflow (Reusable)
299
+ # This workflow is called by downstream repositories.
300
+ #
301
+ # Downstream repos will reference this workflow like:
302
+ # uses: ${repoFullName}/.github/workflows/sync-reusable.yml@v1.0.0
303
+ #
304
+ # TOKEN: Requires a token with read access to the canonical repository.
305
+ # The default GITHUB_TOKEN is used for operations on the downstream repo.
306
+
307
+ name: Sync Reusable
308
+
309
+ on:
310
+ workflow_call:
311
+ inputs:
312
+ force:
313
+ description: 'Force sync even if no updates detected'
314
+ required: false
315
+ default: false
316
+ type: boolean
317
+ commit_strategy:
318
+ description: 'How to commit changes: "pr" (create pull request) or "direct" (commit to current branch)'
319
+ required: false
320
+ default: 'pr'
321
+ type: string
322
+ pr_branch_prefix:
323
+ description: 'Branch prefix for PR branches'
324
+ required: false
325
+ default: '${prefix}/sync'
326
+ type: string
327
+ pr_title:
328
+ description: 'Pull request title'
329
+ required: false
330
+ default: 'chore(${prefix}): sync agent configuration'
331
+ type: string
332
+ reviewers:
333
+ description: 'PR reviewers (comma-separated GitHub usernames)'
334
+ required: false
335
+ type: string
336
+ commit_message:
337
+ description: 'Commit message for direct commits'
338
+ required: false
339
+ default: 'chore(${prefix}): sync agent configuration'
340
+ type: string
341
+ secrets:
342
+ token:
343
+ description: 'GitHub token with read access to the canonical repository'
344
+ required: true
345
+ outputs:
346
+ changes_detected:
347
+ description: 'Whether changes were detected after sync'
348
+ value: \${{ jobs.sync.outputs.changes_detected }}
349
+ pr_number:
350
+ description: 'Pull request number (if PR strategy and changes detected)'
351
+ value: \${{ jobs.sync.outputs.pr_number }}
352
+ pr_url:
353
+ description: 'Pull request URL (if PR strategy and changes detected)'
354
+ value: \${{ jobs.sync.outputs.pr_url }}
355
+
356
+ jobs:
357
+ sync:
358
+ runs-on: ubuntu-latest
359
+ permissions:
360
+ contents: write
361
+ pull-requests: write
362
+ outputs:
363
+ changes_detected: \${{ steps.check-changes.outputs.changes_detected }}
364
+ pr_number: \${{ steps.create-pr.outputs.pr_number }}
365
+ pr_url: \${{ steps.create-pr.outputs.pr_url }}
366
+ steps:
367
+ - name: Checkout
368
+ uses: actions/checkout@v4
369
+ with:
370
+ fetch-depth: 0
371
+
372
+ - name: Setup Node.js
373
+ uses: actions/setup-node@v4
374
+ with:
375
+ node-version: '20'
376
+
377
+ - name: Install agent-conf CLI
378
+ run: npm install -g agent-conf
379
+
380
+ - name: Run sync
381
+ run: agent-conf sync --yes --summary-file /tmp/sync-summary.md --expand-changes
382
+ env:
383
+ GITHUB_TOKEN: \${{ secrets.token }}
384
+
385
+ - name: Check for changes
386
+ id: check-changes
387
+ run: |
388
+ # Check for meaningful changes (excluding lockfile which always updates with synced_at)
389
+ LOCKFILE_PATH=".agent-conf/lockfile.json"
390
+
391
+ # Get changed files excluding lockfile
392
+ MEANINGFUL_CHANGES=$(git status --porcelain | grep -v "^.. $LOCKFILE_PATH$" || true)
393
+
394
+ if [ -n "$MEANINGFUL_CHANGES" ]; then
395
+ echo "changes_detected=true" >> $GITHUB_OUTPUT
396
+ echo "Meaningful changes detected after sync:"
397
+ git status --short
398
+ else
399
+ echo "changes_detected=false" >> $GITHUB_OUTPUT
400
+ echo "No meaningful changes detected (only lockfile updated)"
401
+ fi
402
+
403
+ - name: Configure git
404
+ if: steps.check-changes.outputs.changes_detected == 'true'
405
+ run: |
406
+ git config user.name "github-actions[bot]"
407
+ git config user.email "github-actions[bot]@users.noreply.github.com"
408
+
409
+ - name: Check for existing PR
410
+ id: check-pr
411
+ if: steps.check-changes.outputs.changes_detected == 'true' && inputs.commit_strategy == 'pr'
412
+ env:
413
+ GH_TOKEN: \${{ github.token }}
414
+ run: |
415
+ BRANCH_NAME="\${{ inputs.pr_branch_prefix }}"
416
+
417
+ # Check if there's an existing open PR from this branch
418
+ EXISTING_PR=$(gh pr list --head "$BRANCH_NAME" --state open --json number,url --jq '.[0]' 2>/dev/null || echo "")
419
+
420
+ if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then
421
+ PR_NUMBER=$(echo "$EXISTING_PR" | jq -r '.number')
422
+ PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
423
+ echo "existing_pr=true" >> $GITHUB_OUTPUT
424
+ echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
425
+ echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
426
+ echo "Found existing PR #$PR_NUMBER: $PR_URL"
427
+ else
428
+ echo "existing_pr=false" >> $GITHUB_OUTPUT
429
+ echo "No existing PR found for branch $BRANCH_NAME"
430
+ fi
431
+
432
+ echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
433
+
434
+ - name: Create or update PR branch
435
+ if: steps.check-changes.outputs.changes_detected == 'true' && inputs.commit_strategy == 'pr'
436
+ run: |
437
+ BRANCH_NAME="\${{ inputs.pr_branch_prefix }}"
438
+
439
+ # Check if remote branch exists
440
+ if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
441
+ # Branch exists - fetch and reset to it, then apply our changes
442
+ git fetch origin "$BRANCH_NAME"
443
+ git checkout -B "$BRANCH_NAME" origin/"$BRANCH_NAME"
444
+ # Reset to match the base branch, then apply changes
445
+ git reset --soft \${{ github.ref_name }}
446
+ else
447
+ # Create new branch
448
+ git checkout -b "$BRANCH_NAME"
449
+ fi
450
+
451
+ git add -A
452
+ git commit -m "\${{ inputs.pr_title }}" || echo "No changes to commit"
453
+ git push --force-with-lease -u origin "$BRANCH_NAME"
454
+
455
+ - name: Create or update pull request
456
+ id: create-pr
457
+ if: steps.check-changes.outputs.changes_detected == 'true' && inputs.commit_strategy == 'pr'
458
+ env:
459
+ GH_TOKEN: \${{ github.token }}
460
+ run: |
461
+ # Read sync summary if available
462
+ if [ -f /tmp/sync-summary.md ]; then
463
+ SYNC_SUMMARY=$(cat /tmp/sync-summary.md)
464
+ else
465
+ SYNC_SUMMARY="## Changes
466
+ - Synced agent configuration from canonical repository"
467
+ fi
468
+
469
+ PR_BODY="This PR was automatically created by the ${prefix} sync workflow.
470
+
471
+ $SYNC_SUMMARY
472
+
473
+ ---
474
+ *This is an automated PR. Review the changes and merge when ready.*"
475
+
476
+ if [ "\${{ steps.check-pr.outputs.existing_pr }}" == "true" ]; then
477
+ # Update existing PR body
478
+ PR_NUMBER="\${{ steps.check-pr.outputs.pr_number }}"
479
+ PR_URL="\${{ steps.check-pr.outputs.pr_url }}"
480
+
481
+ gh pr edit "$PR_NUMBER" --body "$PR_BODY"
482
+ echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
483
+ echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
484
+ echo "Updated existing PR #$PR_NUMBER: $PR_URL"
485
+ else
486
+ # Create new PR
487
+ REVIEWERS_ARG=""
488
+ if [ -n "\${{ inputs.reviewers }}" ]; then
489
+ REVIEWERS_ARG="--reviewer \${{ inputs.reviewers }}"
490
+ fi
491
+
492
+ PR_URL=$(gh pr create \\
493
+ --title "\${{ inputs.pr_title }}" \\
494
+ --body "$PR_BODY" \\
495
+ $REVIEWERS_ARG)
496
+
497
+ PR_NUMBER=$(gh pr view --json number -q .number)
498
+ echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
499
+ echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
500
+ echo "Created PR #$PR_NUMBER: $PR_URL"
501
+ fi
502
+
503
+ - name: Commit directly to branch
504
+ if: steps.check-changes.outputs.changes_detected == 'true' && inputs.commit_strategy == 'direct'
505
+ run: |
506
+ git add -A
507
+ git commit -m "\${{ inputs.commit_message }}"
508
+ git push
509
+ echo "Changes committed directly to $(git branch --show-current)"
510
+ `;
511
+ }
512
+ function generateCheckWorkflow(repoFullName, prefix) {
513
+ return `# ${prefix} File Integrity Check (Reusable)
514
+ # This workflow is called by downstream repositories.
515
+ #
516
+ # Downstream repos will reference this workflow like:
517
+ # uses: ${repoFullName}/.github/workflows/check-reusable.yml@v1.0.0
518
+
519
+ name: Check Reusable
520
+
521
+ on:
522
+ workflow_call:
523
+
524
+ jobs:
525
+ check:
526
+ runs-on: ubuntu-latest
527
+ steps:
528
+ - name: Checkout
529
+ uses: actions/checkout@v4
530
+
531
+ - name: Setup Node.js
532
+ uses: actions/setup-node@v4
533
+ with:
534
+ node-version: '20'
535
+
536
+ - name: Install agent-conf CLI
537
+ run: npm install -g agent-conf
538
+
539
+ - name: Check file integrity
540
+ run: agent-conf check
541
+ `;
542
+ }
543
+ async function canonicalInitCommand(options) {
544
+ const logger = createLogger();
545
+ console.log();
546
+ prompts.intro(pc2.bold("agent-conf canonical init"));
547
+ const targetDir = options.dir ? path3.resolve(options.dir) : process.cwd();
548
+ const dirName = path3.basename(targetDir);
549
+ const cwd = process.cwd();
550
+ let isAtGitRoot = await isGitRoot(targetDir);
551
+ let gitProjectName = await getGitProjectName(targetDir);
552
+ let gitOrganization = await getGitOrganization(targetDir);
553
+ if (!gitProjectName) {
554
+ const cwdIsGitRoot = await isGitRoot(cwd);
555
+ const cwdGitProjectName = await getGitProjectName(cwd);
556
+ const cwdGitOrganization = await getGitOrganization(cwd);
557
+ if (cwdIsGitRoot && path3.resolve(targetDir) === path3.resolve(cwd)) {
558
+ isAtGitRoot = true;
559
+ gitProjectName = cwdGitProjectName;
560
+ gitOrganization = cwdGitOrganization;
561
+ } else if (cwdGitOrganization && !gitOrganization) {
562
+ gitOrganization = cwdGitOrganization;
563
+ }
564
+ }
565
+ let defaultName;
566
+ let nameHint;
567
+ if (isAtGitRoot && gitProjectName) {
568
+ defaultName = gitProjectName;
569
+ nameHint = " (from current git project)";
570
+ } else {
571
+ defaultName = dirName;
572
+ nameHint = "";
573
+ }
574
+ const dirExists = await directoryExists(targetDir);
575
+ if (dirExists) {
576
+ const configExists = await fileExists(path3.join(targetDir, "agent-conf.yaml"));
577
+ if (configExists && !options.yes) {
578
+ const shouldContinue = await prompts.confirm({
579
+ message: "This directory already has an agent-conf.yaml. Overwrite?",
580
+ initialValue: false
581
+ });
582
+ if (prompts.isCancel(shouldContinue) || !shouldContinue) {
583
+ prompts.cancel("Operation cancelled");
584
+ process.exit(0);
585
+ }
586
+ }
587
+ }
588
+ let resolvedOptions;
589
+ if (options.yes) {
590
+ resolvedOptions = {
591
+ name: options.name ?? defaultName,
592
+ organization: options.org ?? gitOrganization,
593
+ targetDir,
594
+ markerPrefix: options.markerPrefix ?? "agent-conf",
595
+ includeExamples: options.includeExamples !== false,
596
+ rulesDir: options.rulesDir || void 0
597
+ };
598
+ } else {
599
+ const name = await prompts.text({
600
+ message: `Canonical repository name${nameHint}:`,
601
+ placeholder: defaultName,
602
+ defaultValue: options.name ?? defaultName,
603
+ validate: (value) => {
604
+ if (!value.trim()) return "Name is required";
605
+ if (!/^[a-z0-9-]+$/.test(value)) return "Name must be lowercase alphanumeric with hyphens";
606
+ return void 0;
607
+ }
608
+ });
609
+ if (prompts.isCancel(name)) {
610
+ prompts.cancel("Operation cancelled");
611
+ process.exit(0);
612
+ }
613
+ const orgDefault = options.org ?? gitOrganization;
614
+ const orgHint = gitOrganization && !options.org ? " (from git)" : "";
615
+ const organization = await prompts.text({
616
+ message: `Organization name${orgHint} (optional):`,
617
+ placeholder: orgDefault ?? "ACME Corp",
618
+ ...orgDefault ? { defaultValue: orgDefault } : {}
619
+ });
620
+ if (prompts.isCancel(organization)) {
621
+ prompts.cancel("Operation cancelled");
622
+ process.exit(0);
623
+ }
624
+ const markerPrefix = await prompts.text({
625
+ message: "Marker prefix for managed content:",
626
+ placeholder: "agent-conf",
627
+ defaultValue: options.markerPrefix ?? "agent-conf",
628
+ validate: (value) => {
629
+ if (!value.trim()) return "Prefix is required";
630
+ if (!/^[a-z0-9-]+$/.test(value))
631
+ return "Prefix must be lowercase alphanumeric with hyphens";
632
+ return void 0;
633
+ }
634
+ });
635
+ if (prompts.isCancel(markerPrefix)) {
636
+ prompts.cancel("Operation cancelled");
637
+ process.exit(0);
638
+ }
639
+ const includeExamples = await prompts.confirm({
640
+ message: "Include example skill?",
641
+ initialValue: options.includeExamples !== false
642
+ });
643
+ if (prompts.isCancel(includeExamples)) {
644
+ prompts.cancel("Operation cancelled");
645
+ process.exit(0);
646
+ }
647
+ const includeRules = await prompts.confirm({
648
+ message: "Include rules directory?",
649
+ initialValue: false
650
+ });
651
+ if (prompts.isCancel(includeRules)) {
652
+ prompts.cancel("Operation cancelled");
653
+ process.exit(0);
654
+ }
655
+ let rulesDir;
656
+ if (includeRules) {
657
+ const rulesDirInput = await prompts.text({
658
+ message: "Rules directory name:",
659
+ placeholder: "rules",
660
+ defaultValue: "rules",
661
+ validate: (value) => {
662
+ if (!value.trim()) return "Directory name is required";
663
+ if (!/^[a-z0-9-_/]+$/.test(value))
664
+ return "Directory name must be lowercase alphanumeric with hyphens, underscores, or slashes";
665
+ return void 0;
666
+ }
667
+ });
668
+ if (prompts.isCancel(rulesDirInput)) {
669
+ prompts.cancel("Operation cancelled");
670
+ process.exit(0);
671
+ }
672
+ rulesDir = rulesDirInput;
673
+ }
674
+ resolvedOptions = {
675
+ name,
676
+ organization: organization || void 0,
677
+ targetDir,
678
+ markerPrefix,
679
+ includeExamples,
680
+ rulesDir
681
+ };
682
+ }
683
+ const spinner = logger.spinner("Creating canonical repository structure...");
684
+ spinner.start();
685
+ try {
686
+ await ensureDir(resolvedOptions.targetDir);
687
+ const instructionsDir = path3.join(resolvedOptions.targetDir, "instructions");
688
+ const skillsDir = path3.join(resolvedOptions.targetDir, "skills");
689
+ const workflowsDir = path3.join(resolvedOptions.targetDir, ".github", "workflows");
690
+ await ensureDir(instructionsDir);
691
+ await ensureDir(skillsDir);
692
+ await ensureDir(workflowsDir);
693
+ if (resolvedOptions.rulesDir) {
694
+ const rulesDir = path3.join(resolvedOptions.targetDir, resolvedOptions.rulesDir);
695
+ await ensureDir(rulesDir);
696
+ await fs3.writeFile(path3.join(rulesDir, ".gitkeep"), "", "utf-8");
697
+ }
698
+ const configPath = path3.join(resolvedOptions.targetDir, "agent-conf.yaml");
699
+ await fs3.writeFile(configPath, generateConfigYaml(resolvedOptions), "utf-8");
700
+ const agentsMdPath = path3.join(instructionsDir, "AGENTS.md");
701
+ await fs3.writeFile(agentsMdPath, generateAgentsMd(resolvedOptions), "utf-8");
702
+ if (resolvedOptions.includeExamples) {
703
+ const exampleSkillDir = path3.join(skillsDir, "example-skill");
704
+ const referencesDir = path3.join(exampleSkillDir, "references");
705
+ await ensureDir(referencesDir);
706
+ const skillMdPath = path3.join(exampleSkillDir, "SKILL.md");
707
+ await fs3.writeFile(skillMdPath, generateExampleSkillMd(), "utf-8");
708
+ const gitkeepPath = path3.join(referencesDir, ".gitkeep");
709
+ await fs3.writeFile(gitkeepPath, "", "utf-8");
710
+ }
711
+ const syncWorkflowPath = path3.join(workflowsDir, "sync-reusable.yml");
712
+ const checkWorkflowPath = path3.join(workflowsDir, "check-reusable.yml");
713
+ const repoFullName = resolvedOptions.organization ? `${resolvedOptions.organization}/${resolvedOptions.name}` : resolvedOptions.name;
714
+ await fs3.writeFile(
715
+ syncWorkflowPath,
716
+ generateSyncWorkflow(repoFullName, resolvedOptions.markerPrefix),
717
+ "utf-8"
718
+ );
719
+ await fs3.writeFile(
720
+ checkWorkflowPath,
721
+ generateCheckWorkflow(repoFullName, resolvedOptions.markerPrefix),
722
+ "utf-8"
723
+ );
724
+ spinner.succeed("Canonical repository structure created");
725
+ console.log();
726
+ console.log(pc2.bold("Created:"));
727
+ console.log(` ${pc2.green("+")} ${formatPath(configPath)}`);
728
+ console.log(` ${pc2.green("+")} ${formatPath(agentsMdPath)}`);
729
+ if (resolvedOptions.includeExamples) {
730
+ console.log(
731
+ ` ${pc2.green("+")} ${formatPath(path3.join(skillsDir, "example-skill/SKILL.md"))}`
732
+ );
733
+ }
734
+ if (resolvedOptions.rulesDir) {
735
+ console.log(
736
+ ` ${pc2.green("+")} ${formatPath(path3.join(resolvedOptions.targetDir, resolvedOptions.rulesDir))}/`
737
+ );
738
+ }
739
+ console.log(` ${pc2.green("+")} ${formatPath(syncWorkflowPath)}`);
740
+ console.log(` ${pc2.green("+")} ${formatPath(checkWorkflowPath)}`);
741
+ console.log();
742
+ console.log(pc2.dim(`Name: ${resolvedOptions.name}`));
743
+ if (resolvedOptions.organization) {
744
+ console.log(pc2.dim(`Organization: ${resolvedOptions.organization}`));
745
+ }
746
+ console.log(pc2.dim(`Marker prefix: ${resolvedOptions.markerPrefix}`));
747
+ console.log();
748
+ console.log(pc2.bold("Next steps:"));
749
+ console.log(` 1. Edit ${pc2.cyan("instructions/AGENTS.md")} with your engineering standards`);
750
+ console.log(` 2. Add skills to ${pc2.cyan("skills/")} directory`);
751
+ if (resolvedOptions.rulesDir) {
752
+ console.log(` 3. Add rules to ${pc2.cyan(`${resolvedOptions.rulesDir}/`)} directory`);
753
+ console.log(` 4. Commit and push to create your canonical repository`);
754
+ } else {
755
+ console.log(` 3. Commit and push to create your canonical repository`);
756
+ }
757
+ console.log();
758
+ console.log(
759
+ pc2.dim(
760
+ `See https://github.com/julian-pani/agent-conf/blob/master/cli/docs/CANONICAL_REPOSITORY_SETUP.md for detailed setup instructions.`
761
+ )
762
+ );
763
+ prompts.outro(pc2.green("Done!"));
764
+ } catch (error) {
765
+ spinner.fail("Failed to create canonical repository");
766
+ logger.error(error instanceof Error ? error.message : String(error));
767
+ process.exit(1);
768
+ }
769
+ }
770
+
771
+ // src/commands/check.ts
772
+ import * as fs5 from "fs/promises";
773
+ import * as path5 from "path";
774
+ import pc3 from "picocolors";
775
+
776
+ // src/core/lockfile.ts
777
+ import { createHash } from "crypto";
778
+ import * as fs4 from "fs/promises";
779
+ import * as path4 from "path";
780
+
781
+ // src/schemas/lockfile.ts
782
+ import { z } from "zod";
783
+ var CURRENT_LOCKFILE_VERSION = "1.0.0";
784
+ var RulesContentSchema = z.object({
785
+ /** List of rule file paths synced (relative to rules dir) */
786
+ files: z.array(z.string()),
787
+ /** Hash of all rules content */
788
+ content_hash: z.string()
789
+ });
790
+ var SourceSchema = z.discriminatedUnion("type", [
791
+ z.object({
792
+ type: z.literal("github"),
793
+ repository: z.string(),
794
+ commit_sha: z.string(),
795
+ ref: z.string()
796
+ }),
797
+ z.object({
798
+ type: z.literal("local"),
799
+ path: z.string(),
800
+ commit_sha: z.string().optional()
801
+ })
802
+ ]);
803
+ var ContentSchema = z.object({
804
+ agents_md: z.object({
805
+ global_block_hash: z.string(),
806
+ merged: z.boolean()
807
+ }),
808
+ skills: z.array(z.string()),
809
+ targets: z.array(z.string()).optional(),
810
+ /** Marker prefix used for managed content (default: "agent-conf") */
811
+ marker_prefix: z.string().optional(),
812
+ /** Rules content tracking - optional for backward compat */
813
+ rules: RulesContentSchema.optional()
814
+ });
815
+ var LockfileSchema = z.object({
816
+ /** Schema version in semver format (e.g., "1.0.0") */
817
+ version: z.string().regex(/^\d+\.\d+\.\d+$/, "Version must be in semver format (e.g., 1.0.0)"),
818
+ /** Pinned release version of the canonical source (e.g., "1.2.0") */
819
+ pinned_version: z.string().optional(),
820
+ synced_at: z.string().datetime(),
821
+ source: SourceSchema,
822
+ content: ContentSchema,
823
+ /** CLI version used for sync (optional, for diagnostics only) */
824
+ cli_version: z.string().optional()
825
+ });
826
+
827
+ // src/core/schema.ts
828
+ var SUPPORTED_SCHEMA_VERSION = "1.0.0";
829
+ function checkSchemaCompatibility(contentVersion) {
830
+ const contentParts = contentVersion.split(".").map(Number);
831
+ const supportedParts = SUPPORTED_SCHEMA_VERSION.split(".").map(Number);
832
+ const contentMajor = contentParts[0] ?? 0;
833
+ const contentMinor = contentParts[1] ?? 0;
834
+ const supportedMajor = supportedParts[0] ?? 0;
835
+ const supportedMinor = supportedParts[1] ?? 0;
836
+ if (contentMajor > supportedMajor) {
837
+ return {
838
+ compatible: false,
839
+ error: `Schema version ${contentVersion} requires a newer CLI. Run: npm install -g agent-conf@latest`
840
+ };
841
+ }
842
+ if (contentMajor < supportedMajor) {
843
+ return {
844
+ compatible: false,
845
+ error: `Schema version ${contentVersion} is outdated and no longer supported. This content was created with an older version of agent-conf.`
846
+ };
847
+ }
848
+ if (contentMinor > supportedMinor) {
849
+ return {
850
+ compatible: true,
851
+ warning: `Content uses schema ${contentVersion}, CLI supports ${SUPPORTED_SCHEMA_VERSION}. Some features may not work. Consider upgrading: npm install -g agent-conf@latest`
852
+ };
853
+ }
854
+ return { compatible: true };
855
+ }
856
+
857
+ // src/core/lockfile.ts
858
+ var CONFIG_DIR = ".agent-conf";
859
+ var LOCKFILE_NAME = "lockfile.json";
860
+ function getLockfilePath(targetDir) {
861
+ return path4.join(targetDir, CONFIG_DIR, LOCKFILE_NAME);
862
+ }
863
+ async function readLockfile(targetDir) {
864
+ const lockfilePath = getLockfilePath(targetDir);
865
+ try {
866
+ const content = await fs4.readFile(lockfilePath, "utf-8");
867
+ const parsed = JSON.parse(content);
868
+ const lockfile = LockfileSchema.parse(parsed);
869
+ const schemaCompatibility = checkSchemaCompatibility(lockfile.version);
870
+ return { lockfile, schemaCompatibility };
871
+ } catch (error) {
872
+ if (error.code === "ENOENT") {
873
+ return null;
874
+ }
875
+ throw error;
876
+ }
877
+ }
878
+ async function writeLockfile(targetDir, options) {
879
+ const lockfilePath = getLockfilePath(targetDir);
880
+ const lockfile = {
881
+ version: CURRENT_LOCKFILE_VERSION,
882
+ pinned_version: options.pinnedVersion,
883
+ synced_at: (/* @__PURE__ */ new Date()).toISOString(),
884
+ source: options.source,
885
+ content: {
886
+ agents_md: {
887
+ global_block_hash: hashContent(options.globalBlockContent),
888
+ merged: true
889
+ },
890
+ skills: options.skills,
891
+ targets: options.targets ?? ["claude"],
892
+ marker_prefix: options.markerPrefix,
893
+ rules: options.rules
894
+ },
895
+ cli_version: getCliVersion()
896
+ };
897
+ await fs4.mkdir(path4.dirname(lockfilePath), { recursive: true });
898
+ await fs4.writeFile(lockfilePath, `${JSON.stringify(lockfile, null, 2)}
899
+ `, "utf-8");
900
+ return lockfile;
901
+ }
902
+ function hashContent(content) {
903
+ const hash = createHash("sha256").update(content).digest("hex");
904
+ return `sha256:${hash.slice(0, 12)}`;
905
+ }
906
+ function getCliVersion() {
907
+ return true ? "0.2.0" : "0.0.0";
908
+ }
909
+ async function checkCliVersionMismatch(targetDir) {
910
+ const result = await readLockfile(targetDir);
911
+ if (!result) {
912
+ return null;
913
+ }
914
+ const currentVersion = getCliVersion();
915
+ const lockfileVersion = result.lockfile.cli_version;
916
+ if (!currentVersion || !lockfileVersion) {
917
+ return null;
918
+ }
919
+ const current = currentVersion.split(".").map(Number);
920
+ const lockfile_ = lockfileVersion.split(".").map(Number);
921
+ for (let i = 0; i < 3; i++) {
922
+ if ((lockfile_[i] || 0) > (current[i] || 0)) {
923
+ return {
924
+ currentVersion,
925
+ lockfileVersion
926
+ };
927
+ }
928
+ if ((current[i] || 0) > (lockfile_[i] || 0)) {
929
+ return null;
930
+ }
931
+ }
932
+ return null;
933
+ }
934
+
935
+ // src/commands/check.ts
936
+ async function checkCommand(options = {}) {
937
+ const targetDir = process.cwd();
938
+ const result = await readLockfile(targetDir);
939
+ if (!result) {
940
+ if (!options.quiet) {
941
+ console.log();
942
+ console.log(pc3.yellow("Not synced"));
943
+ console.log();
944
+ console.log(pc3.dim("This repository has not been synced with agent-conf."));
945
+ console.log(pc3.dim("Run `agent-conf init` to sync engineering standards."));
946
+ console.log();
947
+ }
948
+ return;
949
+ }
950
+ const { lockfile, schemaCompatibility } = result;
951
+ if (!schemaCompatibility.compatible) {
952
+ if (!options.quiet) {
953
+ console.log();
954
+ console.log(pc3.red(`Schema error: ${schemaCompatibility.error}`));
955
+ console.log();
956
+ }
957
+ process.exit(1);
958
+ }
959
+ if (schemaCompatibility.warning && !options.quiet) {
960
+ console.log();
961
+ console.log(pc3.yellow(`Warning: ${schemaCompatibility.warning}`));
962
+ console.log();
963
+ }
964
+ const targets = lockfile.content.targets ?? ["claude"];
965
+ const markerPrefix = lockfile.content.marker_prefix;
966
+ const modifiedFiles = [];
967
+ const checkOptions = markerPrefix ? { markerPrefix, metadataPrefix: markerPrefix } : {};
968
+ const allFiles = await checkAllManagedFiles(targetDir, targets, checkOptions);
969
+ const keyPrefix = markerPrefix ? `${markerPrefix.replace(/-/g, "_")}_` : "agent_conf_";
970
+ for (const file of allFiles) {
971
+ if (!file.hasChanges) continue;
972
+ if (file.type === "agents") {
973
+ const agentsMdPath = path5.join(targetDir, "AGENTS.md");
974
+ const content = await fs5.readFile(agentsMdPath, "utf-8");
975
+ const parsed = parseAgentsMd(content, markerPrefix ? { prefix: markerPrefix } : void 0);
976
+ if (parsed.globalBlock) {
977
+ const metadata = parseGlobalBlockMetadata(parsed.globalBlock);
978
+ const contentWithoutMeta = stripMetadataComments(parsed.globalBlock);
979
+ const currentHash = computeGlobalBlockHash(contentWithoutMeta);
980
+ modifiedFiles.push({
981
+ path: "AGENTS.md",
982
+ type: "agents",
983
+ expectedHash: metadata.contentHash ?? "unknown",
984
+ currentHash
985
+ });
986
+ }
987
+ } else if (file.type === "skill") {
988
+ const skillPath = path5.join(targetDir, file.path);
989
+ const content = await fs5.readFile(skillPath, "utf-8");
990
+ const { frontmatter } = parseFrontmatter(content);
991
+ const metadata = frontmatter.metadata;
992
+ const storedHash = metadata?.[`${keyPrefix}content_hash`] ?? "unknown";
993
+ const currentHash = computeContentHash(
994
+ content,
995
+ markerPrefix ? { metadataPrefix: markerPrefix } : void 0
996
+ );
997
+ modifiedFiles.push({
998
+ path: file.path,
999
+ type: "skill",
1000
+ expectedHash: storedHash,
1001
+ currentHash
1002
+ });
1003
+ } else if (file.type === "rule") {
1004
+ const rulePath = path5.join(targetDir, file.path);
1005
+ const content = await fs5.readFile(rulePath, "utf-8");
1006
+ const { frontmatter } = parseFrontmatter(content);
1007
+ const metadata = frontmatter.metadata;
1008
+ const storedHash = metadata?.[`${keyPrefix}content_hash`] ?? "unknown";
1009
+ const currentHash = computeContentHash(
1010
+ content,
1011
+ markerPrefix ? { metadataPrefix: markerPrefix } : void 0
1012
+ );
1013
+ const ruleInfo = {
1014
+ path: file.path,
1015
+ type: "rule",
1016
+ expectedHash: storedHash,
1017
+ currentHash
1018
+ };
1019
+ if (file.rulePath) {
1020
+ ruleInfo.rulePath = file.rulePath;
1021
+ }
1022
+ modifiedFiles.push(ruleInfo);
1023
+ } else if (file.type === "rules-section") {
1024
+ const agentsMdPath = path5.join(targetDir, "AGENTS.md");
1025
+ const content = await fs5.readFile(agentsMdPath, "utf-8");
1026
+ const parsed = parseRulesSection(
1027
+ content,
1028
+ markerPrefix ? { prefix: markerPrefix } : void 0
1029
+ );
1030
+ if (parsed.content) {
1031
+ const metadata = parseRulesSectionMetadata(parsed.content);
1032
+ const contentWithoutMeta = stripRulesSectionMetadata(parsed.content);
1033
+ const currentHash = computeRulesSectionHash(contentWithoutMeta);
1034
+ modifiedFiles.push({
1035
+ path: "AGENTS.md",
1036
+ type: "rules-section",
1037
+ expectedHash: metadata.contentHash ?? "unknown",
1038
+ currentHash
1039
+ });
1040
+ }
1041
+ }
1042
+ }
1043
+ if (allFiles.length === 0) {
1044
+ if (options.quiet) {
1045
+ process.exit(1);
1046
+ }
1047
+ console.log();
1048
+ console.log(pc3.bold("agent-conf check"));
1049
+ console.log();
1050
+ console.log(`${pc3.red("\u2717")} No managed files found`);
1051
+ console.log();
1052
+ console.log(pc3.dim("This repository appears to be synced but no managed files were detected."));
1053
+ if (markerPrefix) {
1054
+ console.log(pc3.dim(`Expected marker prefix: ${markerPrefix}`));
1055
+ }
1056
+ console.log(pc3.dim("Run 'agent-conf sync' to restore the managed files."));
1057
+ console.log();
1058
+ process.exit(1);
1059
+ }
1060
+ if (options.quiet) {
1061
+ if (modifiedFiles.length > 0) {
1062
+ process.exit(1);
1063
+ }
1064
+ return;
1065
+ }
1066
+ console.log();
1067
+ console.log(pc3.bold("agent-conf check"));
1068
+ console.log();
1069
+ console.log("Checking managed files...");
1070
+ console.log();
1071
+ if (modifiedFiles.length === 0) {
1072
+ console.log(`${pc3.green("\u2713")} All managed files are unchanged`);
1073
+ console.log();
1074
+ return;
1075
+ }
1076
+ console.log(`${pc3.red("\u2717")} ${modifiedFiles.length} managed file(s) have been modified:`);
1077
+ console.log();
1078
+ for (const file of modifiedFiles) {
1079
+ let label = "";
1080
+ if (file.type === "agents") {
1081
+ label = " (global block)";
1082
+ } else if (file.type === "rules-section") {
1083
+ label = " (rules section)";
1084
+ } else if (file.type === "rule" && file.rulePath) {
1085
+ label = ` (rule: ${file.rulePath})`;
1086
+ }
1087
+ console.log(` ${file.path}${pc3.dim(label)}`);
1088
+ console.log(` Expected hash: ${pc3.dim(file.expectedHash)}`);
1089
+ console.log(` Current hash: ${pc3.dim(file.currentHash)}`);
1090
+ console.log();
1091
+ }
1092
+ console.log(pc3.dim("These files are managed by agent-conf and should not be modified manually."));
1093
+ console.log(pc3.dim("Run 'agent-conf sync' to restore them to the expected state."));
1094
+ console.log();
1095
+ process.exit(1);
1096
+ }
1097
+
1098
+ // src/commands/completion.ts
1099
+ import fs6 from "fs";
1100
+ import os2 from "os";
1101
+ import path6 from "path";
1102
+ import * as prompts2 from "@clack/prompts";
1103
+ import pc4 from "picocolors";
1104
+ import tabtab from "tabtab";
1105
+ import tabtabInstaller from "tabtab/lib/installer.js";
1106
+ var CLI_NAME = "agent-conf";
1107
+ var COMMANDS = {
1108
+ init: {
1109
+ description: "Initialize or sync agent-conf standards",
1110
+ options: ["-s", "--source", "--local", "-y", "--yes", "--override", "--ref", "-t", "--target"]
1111
+ },
1112
+ sync: {
1113
+ description: "Sync agent-conf standards",
1114
+ options: ["-s", "--source", "--local", "-y", "--yes", "--override", "--ref", "-t", "--target"]
1115
+ },
1116
+ status: {
1117
+ description: "Show current sync status",
1118
+ options: ["-c", "--check"]
1119
+ },
1120
+ update: {
1121
+ description: "Check for and apply updates",
1122
+ options: ["-y", "--yes", "-t", "--target"]
1123
+ },
1124
+ check: {
1125
+ description: "Check if managed files have been modified",
1126
+ options: ["-q", "--quiet"]
1127
+ },
1128
+ config: {
1129
+ description: "Manage global CLI configuration",
1130
+ options: []
1131
+ },
1132
+ "upgrade-cli": {
1133
+ description: "Upgrade the CLI to latest version",
1134
+ options: ["-y", "--yes"]
1135
+ },
1136
+ canonical: {
1137
+ description: "Manage canonical repositories",
1138
+ options: [],
1139
+ subcommands: {
1140
+ init: {
1141
+ description: "Scaffold a new canonical repository",
1142
+ options: [
1143
+ "-n",
1144
+ "--name",
1145
+ "-o",
1146
+ "--org",
1147
+ "-d",
1148
+ "--dir",
1149
+ "--marker-prefix",
1150
+ "--no-examples",
1151
+ "--rules-dir",
1152
+ "-y",
1153
+ "--yes"
1154
+ ]
1155
+ }
1156
+ }
1157
+ },
1158
+ completion: {
1159
+ description: "Manage shell completions",
1160
+ options: []
1161
+ }
1162
+ };
1163
+ var CONFIG_SUBCOMMANDS = ["show", "get", "set"];
1164
+ var COMPLETION_SUBCOMMANDS = ["install", "uninstall"];
1165
+ var CANONICAL_SUBCOMMANDS = ["init"];
1166
+ var TARGET_VALUES = ["claude", "codex"];
1167
+ function handleCompletion() {
1168
+ const env = tabtab.parseEnv(process.env);
1169
+ if (!env.complete) {
1170
+ return false;
1171
+ }
1172
+ const { prev, words } = env;
1173
+ if (prev === CLI_NAME || words === 1) {
1174
+ tabtab.log(
1175
+ Object.entries(COMMANDS).map(([name, info]) => ({
1176
+ name,
1177
+ description: info.description
1178
+ }))
1179
+ );
1180
+ return true;
1181
+ }
1182
+ const commandIndex = 1;
1183
+ const currentCommand = env.line.split(/\s+/)[commandIndex];
1184
+ if (currentCommand === "config" && words === 2) {
1185
+ tabtab.log(
1186
+ CONFIG_SUBCOMMANDS.map((name) => ({
1187
+ name,
1188
+ description: `${name} configuration`
1189
+ }))
1190
+ );
1191
+ return true;
1192
+ }
1193
+ if (currentCommand === "completion" && words === 2) {
1194
+ tabtab.log(
1195
+ COMPLETION_SUBCOMMANDS.map((name) => ({
1196
+ name,
1197
+ description: `${name} shell completions`
1198
+ }))
1199
+ );
1200
+ return true;
1201
+ }
1202
+ if (currentCommand === "canonical" && words === 2) {
1203
+ tabtab.log(
1204
+ CANONICAL_SUBCOMMANDS.map((name) => ({
1205
+ name,
1206
+ description: "Scaffold a new canonical repository"
1207
+ }))
1208
+ );
1209
+ return true;
1210
+ }
1211
+ if (currentCommand === "canonical" && words >= 3) {
1212
+ const subcommand = env.line.split(/\s+/)[2];
1213
+ const canonicalCmd = COMMANDS.canonical;
1214
+ if (subcommand && subcommand in canonicalCmd.subcommands) {
1215
+ const subcommandConfig = canonicalCmd.subcommands[subcommand];
1216
+ if (subcommandConfig) {
1217
+ tabtab.log(subcommandConfig.options);
1218
+ return true;
1219
+ }
1220
+ }
1221
+ }
1222
+ if (prev === "--target" || prev === "-t") {
1223
+ tabtab.log(TARGET_VALUES);
1224
+ return true;
1225
+ }
1226
+ if (currentCommand && currentCommand in COMMANDS) {
1227
+ const command = COMMANDS[currentCommand];
1228
+ tabtab.log(command.options);
1229
+ return true;
1230
+ }
1231
+ tabtab.log(Object.keys(COMMANDS));
1232
+ return true;
1233
+ }
1234
+ function getTabtabCompletionFile(shell) {
1235
+ const home = os2.homedir();
1236
+ const ext = shell === "fish" ? "fish" : shell === "zsh" ? "zsh" : "bash";
1237
+ return path6.join(home, ".config", "tabtab", `${CLI_NAME}.${ext}`);
1238
+ }
1239
+ function isCompletionInstalled() {
1240
+ const shell = detectShell();
1241
+ if (!shell) return false;
1242
+ const completionFile = getTabtabCompletionFile(shell);
1243
+ if (fs6.existsSync(completionFile)) {
1244
+ return true;
1245
+ }
1246
+ const configFile = getShellConfigFile(shell);
1247
+ if (!configFile || !fs6.existsSync(configFile)) return false;
1248
+ try {
1249
+ const content = fs6.readFileSync(configFile, "utf-8");
1250
+ return content.includes(`tabtab source for ${CLI_NAME}`) || content.includes(`begin ${CLI_NAME}`);
1251
+ } catch {
1252
+ return false;
1253
+ }
1254
+ }
1255
+ function detectShell() {
1256
+ const shell = process.env.SHELL || "";
1257
+ if (shell.includes("fish")) return "fish";
1258
+ if (shell.includes("zsh")) return "zsh";
1259
+ if (shell.includes("bash")) return "bash";
1260
+ return null;
1261
+ }
1262
+ function getShellConfigFile(shell) {
1263
+ const home = os2.homedir();
1264
+ switch (shell) {
1265
+ case "zsh":
1266
+ return path6.join(home, ".zshrc");
1267
+ case "bash": {
1268
+ const bashProfile = path6.join(home, ".bash_profile");
1269
+ if (fs6.existsSync(bashProfile)) return bashProfile;
1270
+ return path6.join(home, ".bashrc");
1271
+ }
1272
+ case "fish":
1273
+ return path6.join(home, ".config", "fish", "config.fish");
1274
+ default:
1275
+ return null;
1276
+ }
1277
+ }
1278
+ async function installCompletionForShell(shell) {
1279
+ const location = getShellConfigFile(shell);
1280
+ if (!location) {
1281
+ throw new Error(`Unsupported shell: ${shell}`);
1282
+ }
1283
+ await tabtabInstaller.install({
1284
+ name: CLI_NAME,
1285
+ completer: CLI_NAME,
1286
+ location
1287
+ });
1288
+ }
1289
+ async function installCompletion() {
1290
+ console.log();
1291
+ prompts2.intro(pc4.bold("Installing shell completions"));
1292
+ const shell = detectShell();
1293
+ if (!shell) {
1294
+ prompts2.log.warn("Could not detect shell. Supported shells: bash, zsh, fish");
1295
+ prompts2.outro("Completions not installed");
1296
+ return;
1297
+ }
1298
+ prompts2.log.info(`Detected shell: ${pc4.cyan(shell)}`);
1299
+ if (isCompletionInstalled()) {
1300
+ prompts2.log.success("Shell completions are already installed");
1301
+ prompts2.outro("Nothing to do");
1302
+ return;
1303
+ }
1304
+ try {
1305
+ await installCompletionForShell(shell);
1306
+ prompts2.log.success(`Completions installed for ${pc4.cyan(shell)}`);
1307
+ prompts2.log.info(
1308
+ `Restart your shell or run: ${pc4.cyan(`source ${getShellConfigFile(shell)}`)}`
1309
+ );
1310
+ prompts2.outro("Done!");
1311
+ } catch (error) {
1312
+ prompts2.log.error(`Failed to install completions: ${error}`);
1313
+ prompts2.outro("Installation failed");
1314
+ process.exit(1);
1315
+ }
1316
+ }
1317
+ async function uninstallCompletion() {
1318
+ console.log();
1319
+ prompts2.intro(pc4.bold("Uninstalling shell completions"));
1320
+ try {
1321
+ await tabtab.uninstall({
1322
+ name: CLI_NAME
1323
+ });
1324
+ prompts2.log.success("Shell completions uninstalled");
1325
+ prompts2.outro("Done!");
1326
+ } catch (error) {
1327
+ prompts2.log.error(`Failed to uninstall completions: ${error}`);
1328
+ prompts2.outro("Uninstallation failed");
1329
+ process.exit(1);
1330
+ }
1331
+ }
1332
+ async function promptCompletionInstall() {
1333
+ if (isCompletionInstalled()) {
1334
+ return false;
1335
+ }
1336
+ const shell = detectShell();
1337
+ if (!shell) {
1338
+ return false;
1339
+ }
1340
+ const shouldInstall = await prompts2.confirm({
1341
+ message: `Install shell completions for ${shell}?`
1342
+ });
1343
+ if (prompts2.isCancel(shouldInstall) || !shouldInstall) {
1344
+ prompts2.log.info(`You can install later with: ${pc4.cyan("agent-conf completion install")}`);
1345
+ return false;
1346
+ }
1347
+ try {
1348
+ await installCompletionForShell(shell);
1349
+ prompts2.log.success(`Completions installed for ${pc4.cyan(shell)}`);
1350
+ prompts2.log.info(
1351
+ `Restart your shell or run: ${pc4.cyan(`source ${getShellConfigFile(shell)}`)}`
1352
+ );
1353
+ return true;
1354
+ } catch (error) {
1355
+ prompts2.log.warn(`Could not install completions: ${error}`);
1356
+ prompts2.log.info(`You can try again with: ${pc4.cyan("agent-conf completion install")}`);
1357
+ return false;
1358
+ }
1359
+ }
1360
+
1361
+ // src/commands/config.ts
1362
+ import * as prompts3 from "@clack/prompts";
1363
+ import pc5 from "picocolors";
1364
+ async function configShowCommand() {
1365
+ console.log();
1366
+ prompts3.intro(pc5.bold("agent-conf config"));
1367
+ console.log();
1368
+ console.log(pc5.bold("Global Configuration:"));
1369
+ console.log();
1370
+ console.log(pc5.dim(" No configuration options available."));
1371
+ console.log();
1372
+ console.log(pc5.dim("Config location: ~/.agent-conf/config.json"));
1373
+ prompts3.outro("");
1374
+ }
1375
+ async function configGetCommand(key) {
1376
+ const logger = createLogger();
1377
+ logger.error(`Unknown config key: ${key}`);
1378
+ logger.info("No configuration options available.");
1379
+ process.exit(1);
1380
+ }
1381
+ async function configSetCommand(key, _value) {
1382
+ const logger = createLogger();
1383
+ logger.error(`Unknown config key: ${key}`);
1384
+ logger.info("No configuration options available.");
1385
+ process.exit(1);
1386
+ }
1387
+
1388
+ // src/commands/init.ts
1389
+ import * as prompts5 from "@clack/prompts";
1390
+ import pc7 from "picocolors";
1391
+
1392
+ // src/core/sync.ts
1393
+ import { createHash as createHash3 } from "crypto";
1394
+ import * as fs8 from "fs/promises";
1395
+ import * as path8 from "path";
1396
+ import fg from "fast-glob";
1397
+
1398
+ // src/core/merge.ts
1399
+ import * as fs7 from "fs/promises";
1400
+ import * as path7 from "path";
1401
+ async function readFileIfExists(filePath) {
1402
+ try {
1403
+ return await fs7.readFile(filePath, "utf-8");
1404
+ } catch (error) {
1405
+ if (error.code === "ENOENT") {
1406
+ return null;
1407
+ }
1408
+ throw error;
1409
+ }
1410
+ }
1411
+ async function gatherExistingContent(targetDir) {
1412
+ const agentsMdPath = path7.join(targetDir, "AGENTS.md");
1413
+ const rootClaudeMdPath = path7.join(targetDir, "CLAUDE.md");
1414
+ const dotClaudeClaudeMdPath = path7.join(targetDir, ".claude", "CLAUDE.md");
1415
+ const agentsMd = await readFileIfExists(agentsMdPath);
1416
+ const rootClaudeMd = await readFileIfExists(rootClaudeMdPath);
1417
+ if (rootClaudeMd !== null) {
1418
+ return { agentsMd, claudeMd: rootClaudeMd, claudeMdLocation: "root" };
1419
+ }
1420
+ const dotClaudeClaudeMd = await readFileIfExists(dotClaudeClaudeMdPath);
1421
+ if (dotClaudeClaudeMd !== null) {
1422
+ return { agentsMd, claudeMd: dotClaudeClaudeMd, claudeMdLocation: "dotclaude" };
1423
+ }
1424
+ return { agentsMd, claudeMd: null, claudeMdLocation: null };
1425
+ }
1426
+ function stripAgentsReference(content) {
1427
+ return content.split("\n").filter((line) => {
1428
+ const trimmed = line.trim();
1429
+ return !trimmed.match(/^@(\.\.\/|\.claude\/)?AGENTS\.md$/);
1430
+ }).join("\n").trim();
1431
+ }
1432
+ async function mergeAgentsMd(targetDir, globalContent, _source, options = { override: false }) {
1433
+ const existing = await gatherExistingContent(targetDir);
1434
+ const markerOptions = options.markerPrefix ? { prefix: options.markerPrefix } : void 0;
1435
+ const contentToMerge = [];
1436
+ if (existing.agentsMd !== null && !options.override) {
1437
+ const parsed = parseAgentsMd(existing.agentsMd, markerOptions);
1438
+ if (parsed.hasMarkers) {
1439
+ const repoContent2 = extractRepoBlockContent(parsed);
1440
+ if (repoContent2) {
1441
+ contentToMerge.push(repoContent2);
1442
+ }
1443
+ } else {
1444
+ contentToMerge.push(existing.agentsMd.trim());
1445
+ }
1446
+ }
1447
+ let claudeMdContentForMerge = null;
1448
+ if (existing.claudeMd !== null && !options.override) {
1449
+ const strippedContent = stripAgentsReference(existing.claudeMd);
1450
+ if (strippedContent) {
1451
+ claudeMdContentForMerge = strippedContent;
1452
+ contentToMerge.push(strippedContent);
1453
+ }
1454
+ }
1455
+ const repoContent = contentToMerge.length > 0 ? contentToMerge.join("\n\n") : null;
1456
+ const content = buildAgentsMd(globalContent, repoContent, {}, markerOptions);
1457
+ const merged = !options.override && (existing.agentsMd !== null || existing.claudeMd !== null);
1458
+ const preservedRepoContent = contentToMerge.length > 0;
1459
+ return {
1460
+ content,
1461
+ merged,
1462
+ preservedRepoContent,
1463
+ existingClaudeMdContent: claudeMdContentForMerge,
1464
+ claudeMdLocation: existing.claudeMdLocation
1465
+ };
1466
+ }
1467
+ async function writeAgentsMd(targetDir, content) {
1468
+ const agentsMdPath = path7.join(targetDir, "AGENTS.md");
1469
+ await fs7.writeFile(agentsMdPath, content, "utf-8");
1470
+ }
1471
+ async function ensureClaudeMd(targetDir, existingLocation) {
1472
+ const rootPath = path7.join(targetDir, "CLAUDE.md");
1473
+ const dotClaudePath = path7.join(targetDir, ".claude", "CLAUDE.md");
1474
+ const rootReference = "@AGENTS.md";
1475
+ const dotClaudeReference = "@../AGENTS.md";
1476
+ if (existingLocation === "root") {
1477
+ const existingContent = await readFileIfExists(rootPath);
1478
+ if (existingContent !== null) {
1479
+ if (existingContent.includes(rootReference)) {
1480
+ return { created: false, updated: false, location: "root", contentMerged: false };
1481
+ }
1482
+ await fs7.writeFile(rootPath, `${rootReference}
1483
+ `, "utf-8");
1484
+ return { created: false, updated: true, location: "root", contentMerged: true };
1485
+ }
1486
+ }
1487
+ if (existingLocation === "dotclaude") {
1488
+ const existingContent = await readFileIfExists(dotClaudePath);
1489
+ if (existingContent !== null) {
1490
+ if (existingContent.includes(dotClaudeReference)) {
1491
+ return { created: false, updated: false, location: "dotclaude", contentMerged: false };
1492
+ }
1493
+ await fs7.writeFile(dotClaudePath, `${dotClaudeReference}
1494
+ `, "utf-8");
1495
+ return { created: false, updated: true, location: "dotclaude", contentMerged: true };
1496
+ }
1497
+ }
1498
+ await fs7.mkdir(path7.dirname(dotClaudePath), { recursive: true });
1499
+ await fs7.writeFile(dotClaudePath, `${dotClaudeReference}
1500
+ `, "utf-8");
1501
+ return { created: true, updated: false, location: "dotclaude", contentMerged: false };
1502
+ }
1503
+
1504
+ // src/core/rules.ts
1505
+ import { createHash as createHash2 } from "crypto";
1506
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1507
+ function parseFrontmatter2(content) {
1508
+ const match = content.match(FRONTMATTER_REGEX);
1509
+ if (!match || !match[1]) {
1510
+ return { frontmatter: null, body: content };
1511
+ }
1512
+ const rawYaml = match[1];
1513
+ const body = content.slice(match[0].length);
1514
+ try {
1515
+ const frontmatter = parseSimpleYaml(rawYaml);
1516
+ return { frontmatter, body };
1517
+ } catch {
1518
+ return { frontmatter: null, body: content };
1519
+ }
1520
+ }
1521
+ function parseSimpleYaml(yaml) {
1522
+ const result = {};
1523
+ const lines = yaml.split("\n");
1524
+ let currentKey = null;
1525
+ let currentValue = null;
1526
+ let isArray = false;
1527
+ for (const line of lines) {
1528
+ if (line.trim() === "") continue;
1529
+ if (line.match(/^\s+-\s+/)) {
1530
+ if (currentKey && isArray) {
1531
+ const value = line.replace(/^\s+-\s+/, "").replace(/^["']|["']$/g, "").trim();
1532
+ currentValue.push(value);
1533
+ }
1534
+ continue;
1535
+ }
1536
+ if (line.startsWith(" ") && currentKey && typeof currentValue === "object" && !isArray) {
1537
+ const nestedMatch = line.match(/^\s+(\w+):\s*["']?(.*)["']?$/);
1538
+ if (nestedMatch?.[1] && nestedMatch[2] !== void 0) {
1539
+ const key = nestedMatch[1];
1540
+ const value = nestedMatch[2].replace(/^["']|["']$/g, "");
1541
+ currentValue[key] = value;
1542
+ }
1543
+ continue;
1544
+ }
1545
+ if (currentKey && currentValue !== null) {
1546
+ result[currentKey] = currentValue;
1547
+ currentValue = null;
1548
+ isArray = false;
1549
+ }
1550
+ const match = line.match(/^(\w+):\s*(.*)$/);
1551
+ if (match?.[1] && match[2] !== void 0) {
1552
+ const key = match[1];
1553
+ const value = match[2].trim();
1554
+ currentKey = key;
1555
+ if (value === "") {
1556
+ currentValue = {};
1557
+ isArray = false;
1558
+ } else if (value.startsWith("[") && value.endsWith("]")) {
1559
+ try {
1560
+ currentValue = JSON.parse(value);
1561
+ result[key] = currentValue;
1562
+ currentKey = null;
1563
+ currentValue = null;
1564
+ } catch {
1565
+ result[key] = value.replace(/^["']|["']$/g, "");
1566
+ currentKey = null;
1567
+ currentValue = null;
1568
+ }
1569
+ } else {
1570
+ result[key] = value.replace(/^["']|["']$/g, "");
1571
+ currentKey = null;
1572
+ currentValue = null;
1573
+ }
1574
+ }
1575
+ }
1576
+ if (currentKey && currentValue !== null) {
1577
+ result[currentKey] = currentValue;
1578
+ }
1579
+ return result;
1580
+ }
1581
+ function serializeFrontmatter(frontmatter) {
1582
+ const lines = [];
1583
+ for (const [key, value] of Object.entries(frontmatter)) {
1584
+ if (value === void 0 || value === null) continue;
1585
+ if (Array.isArray(value)) {
1586
+ lines.push(`${key}:`);
1587
+ for (const item of value) {
1588
+ lines.push(` - "${item}"`);
1589
+ }
1590
+ } else if (typeof value === "object") {
1591
+ lines.push(`${key}:`);
1592
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
1593
+ const quotedValue = needsQuoting(String(nestedValue)) ? `"${String(nestedValue)}"` : String(nestedValue);
1594
+ lines.push(` ${nestedKey}: ${quotedValue}`);
1595
+ }
1596
+ } else {
1597
+ const strValue = String(value);
1598
+ const quotedValue = needsQuoting(strValue) ? `"${strValue}"` : strValue;
1599
+ lines.push(`${key}: ${quotedValue}`);
1600
+ }
1601
+ }
1602
+ return lines.join("\n");
1603
+ }
1604
+ function needsQuoting(value) {
1605
+ return value.includes(":") || value.includes("#") || value.includes("@") || value === "true" || value === "false" || /^\d+$/.test(value);
1606
+ }
1607
+ function adjustHeadingLevels(content, increment) {
1608
+ if (!content) return "";
1609
+ let inCodeBlock = false;
1610
+ return content.split("\n").map((line) => {
1611
+ if (line.trim().startsWith("```")) {
1612
+ inCodeBlock = !inCodeBlock;
1613
+ return line;
1614
+ }
1615
+ if (inCodeBlock) {
1616
+ return line;
1617
+ }
1618
+ const headingMatch = line.match(/^(#{1,6})(\s+.*)$/);
1619
+ if (!headingMatch || !headingMatch[1]) {
1620
+ return line;
1621
+ }
1622
+ const hashes = headingMatch[1];
1623
+ const rest = headingMatch[2] || "";
1624
+ const currentLevel = hashes.length;
1625
+ const newLevel = Math.min(6, Math.max(1, currentLevel + increment));
1626
+ return "#".repeat(newLevel) + rest;
1627
+ }).join("\n");
1628
+ }
1629
+ function parseRule(content, relativePath) {
1630
+ const { frontmatter, body } = parseFrontmatter2(content);
1631
+ if (frontmatter) {
1632
+ const pathsMatch = content.match(/^---\r?\n[\s\S]*?paths:\s*\n((?:\s+-\s+.+\n?)+)/m);
1633
+ if (pathsMatch?.[1]) {
1634
+ const pathsContent = pathsMatch[1];
1635
+ const paths = pathsContent.split("\n").filter((line) => line.trim().startsWith("-")).map(
1636
+ (line) => line.replace(/^\s+-\s+/, "").replace(/^["']|["']$/g, "").trim()
1637
+ ).filter((p) => p.length > 0);
1638
+ if (paths.length > 0) {
1639
+ frontmatter.paths = paths;
1640
+ }
1641
+ }
1642
+ }
1643
+ return {
1644
+ relativePath,
1645
+ rawContent: content,
1646
+ frontmatter,
1647
+ body
1648
+ };
1649
+ }
1650
+ function generatePathsComment(paths) {
1651
+ if (!paths || paths.length === 0) return "";
1652
+ if (paths.length === 1) {
1653
+ return `<!-- Applies to: ${paths[0]} -->`;
1654
+ }
1655
+ return `<!-- Applies to:
1656
+ ${paths.map((p) => ` - ${p}`).join("\n")}
1657
+ -->`;
1658
+ }
1659
+ function generateRulesSection(rules, markerPrefix) {
1660
+ const sortedRules = [...rules].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1661
+ const contentParts = [];
1662
+ contentParts.push("# Project Rules");
1663
+ contentParts.push("");
1664
+ for (const rule of sortedRules) {
1665
+ contentParts.push(`<!-- Rule: ${rule.relativePath} -->`);
1666
+ if (rule.frontmatter?.paths && rule.frontmatter.paths.length > 0) {
1667
+ contentParts.push(generatePathsComment(rule.frontmatter.paths));
1668
+ }
1669
+ const adjustedBody = adjustHeadingLevels(rule.body.trim(), 1);
1670
+ contentParts.push(adjustedBody);
1671
+ contentParts.push("");
1672
+ }
1673
+ const contentForHash = contentParts.join("\n").trim();
1674
+ const hash = createHash2("sha256").update(contentForHash).digest("hex");
1675
+ const contentHash = `sha256:${hash.slice(0, 12)}`;
1676
+ const parts = [];
1677
+ parts.push(`<!-- ${markerPrefix}:rules:start -->`);
1678
+ parts.push(`<!-- DO NOT EDIT THIS SECTION - Managed by agent-conf -->`);
1679
+ parts.push(`<!-- Content hash: ${contentHash} -->`);
1680
+ parts.push(`<!-- Rule count: ${rules.length} -->`);
1681
+ parts.push("");
1682
+ parts.push(contentForHash);
1683
+ parts.push("");
1684
+ parts.push(`<!-- ${markerPrefix}:rules:end -->`);
1685
+ return parts.join("\n");
1686
+ }
1687
+ function updateAgentsMdWithRules(agentsMdContent, rulesSection, markerPrefix) {
1688
+ const rulesStartMarker = `<!-- ${markerPrefix}:rules:start -->`;
1689
+ const rulesEndMarker = `<!-- ${markerPrefix}:rules:end -->`;
1690
+ const startIdx = agentsMdContent.indexOf(rulesStartMarker);
1691
+ const endIdx = agentsMdContent.indexOf(rulesEndMarker);
1692
+ if (startIdx !== -1 && endIdx !== -1) {
1693
+ const before = agentsMdContent.slice(0, startIdx);
1694
+ const after = agentsMdContent.slice(endIdx + rulesEndMarker.length);
1695
+ return before + rulesSection + after;
1696
+ }
1697
+ const globalEndMarker = `<!-- ${markerPrefix}:global:end -->`;
1698
+ const repoStartMarker = `<!-- ${markerPrefix}:repo:start -->`;
1699
+ const globalEndIdx = agentsMdContent.indexOf(globalEndMarker);
1700
+ if (globalEndIdx !== -1) {
1701
+ const insertPoint = globalEndIdx + globalEndMarker.length;
1702
+ const before = agentsMdContent.slice(0, insertPoint);
1703
+ const after = agentsMdContent.slice(insertPoint);
1704
+ return `${before}
1705
+
1706
+ ${rulesSection}${after}`;
1707
+ }
1708
+ const repoStartIdx = agentsMdContent.indexOf(repoStartMarker);
1709
+ if (repoStartIdx !== -1) {
1710
+ const before = agentsMdContent.slice(0, repoStartIdx);
1711
+ const after = agentsMdContent.slice(repoStartIdx);
1712
+ return `${before}${rulesSection}
1713
+
1714
+ ${after}`;
1715
+ }
1716
+ return `${agentsMdContent.trimEnd()}
1717
+
1718
+ ${rulesSection}
1719
+ `;
1720
+ }
1721
+ function addRuleMetadata(rule, metadataPrefix) {
1722
+ const managedKey = `${metadataPrefix}_managed`;
1723
+ const hashKey = `${metadataPrefix}_content_hash`;
1724
+ const sourceKey = `${metadataPrefix}_source_path`;
1725
+ const hashMetadataPrefix = metadataPrefix.replace(/_/g, "-");
1726
+ const contentHash = computeContentHash(rule.rawContent, {
1727
+ metadataPrefix: hashMetadataPrefix
1728
+ });
1729
+ const existingFrontmatter = rule.frontmatter || {};
1730
+ const existingMetadata = existingFrontmatter.metadata || {};
1731
+ const newMetadata = {
1732
+ ...existingMetadata,
1733
+ [managedKey]: "true",
1734
+ [hashKey]: contentHash,
1735
+ [sourceKey]: rule.relativePath
1736
+ };
1737
+ const newFrontmatter = {};
1738
+ for (const [key, value] of Object.entries(existingFrontmatter)) {
1739
+ if (key !== "metadata") {
1740
+ newFrontmatter[key] = value;
1741
+ }
1742
+ }
1743
+ newFrontmatter.metadata = newMetadata;
1744
+ const yamlContent = serializeFrontmatter(newFrontmatter);
1745
+ return `---
1746
+ ${yamlContent}
1747
+ ---
1748
+ ${rule.body}`;
1749
+ }
1750
+
1751
+ // src/core/targets.ts
1752
+ var SUPPORTED_TARGETS = ["claude", "codex"];
1753
+ var TARGET_CONFIGS = {
1754
+ claude: {
1755
+ dir: ".claude",
1756
+ instructionsFile: "CLAUDE.md"
1757
+ },
1758
+ codex: {
1759
+ dir: ".codex",
1760
+ instructionsFile: null
1761
+ // Codex reads AGENTS.md directly
1762
+ }
1763
+ };
1764
+ function isValidTarget(target) {
1765
+ return SUPPORTED_TARGETS.includes(target);
1766
+ }
1767
+ function parseTargets(input) {
1768
+ const targets = [];
1769
+ for (const t of input) {
1770
+ const parts = t.split(",").map((p) => p.trim().toLowerCase());
1771
+ for (const part of parts) {
1772
+ if (!isValidTarget(part)) {
1773
+ throw new Error(
1774
+ `Invalid target "${part}". Supported targets: ${SUPPORTED_TARGETS.join(", ")}`
1775
+ );
1776
+ }
1777
+ if (!targets.includes(part)) {
1778
+ targets.push(part);
1779
+ }
1780
+ }
1781
+ }
1782
+ return targets.length > 0 ? targets : ["claude"];
1783
+ }
1784
+ function getTargetConfig(target) {
1785
+ return TARGET_CONFIGS[target];
1786
+ }
1787
+
1788
+ // src/core/sync.ts
1789
+ async function discoverRules(rulesDir) {
1790
+ try {
1791
+ await fs8.access(rulesDir);
1792
+ } catch {
1793
+ return [];
1794
+ }
1795
+ const ruleFiles = await fg("**/*.md", {
1796
+ cwd: rulesDir,
1797
+ absolute: false
1798
+ });
1799
+ const rules = [];
1800
+ for (const relativePath of ruleFiles) {
1801
+ const fullPath = path8.join(rulesDir, relativePath);
1802
+ const content = await fs8.readFile(fullPath, "utf-8");
1803
+ rules.push(parseRule(content, relativePath));
1804
+ }
1805
+ return rules;
1806
+ }
1807
+ function computeRulesHash(rules) {
1808
+ if (rules.length === 0) return "";
1809
+ const sorted = [...rules].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
1810
+ const combined = sorted.map((r) => `${r.relativePath}:${r.body}`).join("\n---\n");
1811
+ const hash = createHash3("sha256").update(combined).digest("hex");
1812
+ return hash.slice(0, 16);
1813
+ }
1814
+ async function syncRules(options) {
1815
+ const { sourceRulesPath, targetDir, targets, markerPrefix, metadataPrefix, agentsMdContent } = options;
1816
+ const rules = await discoverRules(sourceRulesPath);
1817
+ const result = {
1818
+ rules,
1819
+ updatedAgentsMd: null,
1820
+ claudeFiles: [],
1821
+ modifiedRules: [],
1822
+ contentHash: ""
1823
+ };
1824
+ if (rules.length === 0) {
1825
+ return result;
1826
+ }
1827
+ result.contentHash = computeRulesHash(rules);
1828
+ if (targets.includes("claude")) {
1829
+ const claudeRulesDir = path8.join(targetDir, ".claude", "rules");
1830
+ for (const rule of rules) {
1831
+ const targetPath = path8.join(claudeRulesDir, rule.relativePath);
1832
+ await fs8.mkdir(path8.dirname(targetPath), { recursive: true });
1833
+ const contentWithMetadata = addRuleMetadata(rule, metadataPrefix);
1834
+ let existingContent = null;
1835
+ try {
1836
+ existingContent = await fs8.readFile(targetPath, "utf-8");
1837
+ } catch {
1838
+ }
1839
+ if (existingContent !== contentWithMetadata) {
1840
+ await fs8.writeFile(targetPath, contentWithMetadata, "utf-8");
1841
+ result.modifiedRules.push(rule.relativePath);
1842
+ }
1843
+ result.claudeFiles.push(rule.relativePath);
1844
+ }
1845
+ }
1846
+ if (targets.includes("codex")) {
1847
+ const rulesSection = generateRulesSection(rules, markerPrefix);
1848
+ result.updatedAgentsMd = updateAgentsMdWithRules(agentsMdContent, rulesSection, markerPrefix);
1849
+ }
1850
+ return result;
1851
+ }
1852
+ async function sync(targetDir, resolvedSource, options = { override: false, targets: ["claude"] }) {
1853
+ const markerPrefix = resolvedSource.markerPrefix;
1854
+ const globalContent = await fs8.readFile(resolvedSource.agentsMdPath, "utf-8");
1855
+ const mergeResult = await mergeAgentsMd(targetDir, globalContent, resolvedSource.source, {
1856
+ override: options.override,
1857
+ markerPrefix
1858
+ });
1859
+ await writeAgentsMd(targetDir, mergeResult.content);
1860
+ const skillDirs = await fg("*/", {
1861
+ cwd: resolvedSource.skillsPath,
1862
+ onlyDirectories: true,
1863
+ deep: 1
1864
+ });
1865
+ const skillNames = skillDirs.map((d) => d.replace(/\/$/, ""));
1866
+ const validationErrors = [];
1867
+ for (const skillName of skillNames) {
1868
+ const skillMdPath = path8.join(resolvedSource.skillsPath, skillName, "SKILL.md");
1869
+ try {
1870
+ const content = await fs8.readFile(skillMdPath, "utf-8");
1871
+ const error = validateSkillFrontmatter(content, skillName, skillMdPath);
1872
+ if (error) {
1873
+ validationErrors.push(error);
1874
+ }
1875
+ } catch {
1876
+ }
1877
+ }
1878
+ const targetResults = [];
1879
+ let totalCopied = 0;
1880
+ const allModifiedSkills = /* @__PURE__ */ new Set();
1881
+ for (const target of options.targets) {
1882
+ const config = getTargetConfig(target);
1883
+ const skillsResult = await syncSkillsToTarget(
1884
+ targetDir,
1885
+ resolvedSource.skillsPath,
1886
+ skillNames,
1887
+ config,
1888
+ markerPrefix
1889
+ );
1890
+ totalCopied += skillsResult.copied;
1891
+ for (const skill of skillsResult.modifiedSkills) {
1892
+ allModifiedSkills.add(skill);
1893
+ }
1894
+ let instructionsResult;
1895
+ if (config.instructionsFile) {
1896
+ instructionsResult = await ensureInstructionsMd(
1897
+ targetDir,
1898
+ config,
1899
+ target === "claude" ? mergeResult.claudeMdLocation : null
1900
+ );
1901
+ } else {
1902
+ instructionsResult = {
1903
+ created: false,
1904
+ updated: false,
1905
+ location: null,
1906
+ contentMerged: false
1907
+ };
1908
+ }
1909
+ targetResults.push({
1910
+ target,
1911
+ instructionsMd: instructionsResult,
1912
+ skills: { copied: skillsResult.copied }
1913
+ });
1914
+ }
1915
+ let rulesResult = null;
1916
+ if (resolvedSource.rulesPath) {
1917
+ const currentAgentsMd = await fs8.readFile(path8.join(targetDir, "AGENTS.md"), "utf-8");
1918
+ rulesResult = await syncRules({
1919
+ sourceRulesPath: resolvedSource.rulesPath,
1920
+ targetDir,
1921
+ targets: options.targets,
1922
+ markerPrefix,
1923
+ metadataPrefix: markerPrefix.replace(/-/g, "_"),
1924
+ agentsMdContent: currentAgentsMd
1925
+ });
1926
+ if (rulesResult.updatedAgentsMd && options.targets.includes("codex")) {
1927
+ await writeAgentsMd(targetDir, rulesResult.updatedAgentsMd);
1928
+ }
1929
+ }
1930
+ const lockfileOptions = {
1931
+ source: resolvedSource.source,
1932
+ globalBlockContent: globalContent,
1933
+ skills: skillNames,
1934
+ targets: options.targets,
1935
+ markerPrefix
1936
+ };
1937
+ if (options.pinnedVersion) {
1938
+ lockfileOptions.pinnedVersion = options.pinnedVersion;
1939
+ }
1940
+ if (rulesResult && rulesResult.rules.length > 0) {
1941
+ lockfileOptions.rules = {
1942
+ files: rulesResult.rules.map((r) => r.relativePath),
1943
+ content_hash: rulesResult.contentHash
1944
+ };
1945
+ }
1946
+ const lockfile = await writeLockfile(targetDir, lockfileOptions);
1947
+ const result = {
1948
+ lockfile,
1949
+ agentsMd: {
1950
+ merged: mergeResult.merged,
1951
+ preservedRepoContent: mergeResult.preservedRepoContent
1952
+ },
1953
+ targets: targetResults,
1954
+ skills: {
1955
+ synced: skillNames,
1956
+ modified: [...allModifiedSkills],
1957
+ totalCopied,
1958
+ validationErrors
1959
+ }
1960
+ };
1961
+ if (rulesResult && rulesResult.rules.length > 0) {
1962
+ result.rules = {
1963
+ synced: rulesResult.rules.map((r) => r.relativePath),
1964
+ modified: rulesResult.modifiedRules,
1965
+ contentHash: rulesResult.contentHash,
1966
+ claudeFiles: rulesResult.claudeFiles,
1967
+ codexUpdated: rulesResult.updatedAgentsMd !== null
1968
+ };
1969
+ }
1970
+ return result;
1971
+ }
1972
+ async function syncSkillsToTarget(targetDir, sourceSkillsPath, skillNames, config, metadataPrefix) {
1973
+ const targetSkillsPath = path8.join(targetDir, config.dir, "skills");
1974
+ let copied = 0;
1975
+ const modifiedSkills = [];
1976
+ for (const skillName of skillNames) {
1977
+ const sourceDir = path8.join(sourceSkillsPath, skillName);
1978
+ const targetSkillDir = path8.join(targetSkillsPath, skillName);
1979
+ const result = await copySkillDirectory(sourceDir, targetSkillDir, metadataPrefix);
1980
+ copied += result.copied;
1981
+ if (result.modified) {
1982
+ modifiedSkills.push(skillName);
1983
+ }
1984
+ }
1985
+ return { copied, modifiedSkills };
1986
+ }
1987
+ async function copySkillDirectory(sourceDir, targetDir, metadataPrefix) {
1988
+ await fs8.mkdir(targetDir, { recursive: true });
1989
+ const entries = await fs8.readdir(sourceDir, { withFileTypes: true });
1990
+ let copied = 0;
1991
+ let modified = false;
1992
+ for (const entry of entries) {
1993
+ const sourcePath = path8.join(sourceDir, entry.name);
1994
+ const targetPath = path8.join(targetDir, entry.name);
1995
+ if (entry.isDirectory()) {
1996
+ const subResult = await copySkillDirectory(sourcePath, targetPath, metadataPrefix);
1997
+ copied += subResult.copied;
1998
+ if (subResult.modified) modified = true;
1999
+ } else if (entry.name === "SKILL.md") {
2000
+ const content = await fs8.readFile(sourcePath, "utf-8");
2001
+ const contentWithMetadata = addManagedMetadata(content, { metadataPrefix });
2002
+ let existingContent = null;
2003
+ try {
2004
+ existingContent = await fs8.readFile(targetPath, "utf-8");
2005
+ } catch {
2006
+ }
2007
+ if (existingContent !== contentWithMetadata) {
2008
+ await fs8.writeFile(targetPath, contentWithMetadata, "utf-8");
2009
+ modified = true;
2010
+ }
2011
+ copied++;
2012
+ } else {
2013
+ let needsCopy = true;
2014
+ try {
2015
+ const [sourceContent, targetContent] = await Promise.all([
2016
+ fs8.readFile(sourcePath),
2017
+ fs8.readFile(targetPath)
2018
+ ]);
2019
+ needsCopy = !sourceContent.equals(targetContent);
2020
+ } catch {
2021
+ }
2022
+ if (needsCopy) {
2023
+ await fs8.copyFile(sourcePath, targetPath);
2024
+ modified = true;
2025
+ }
2026
+ copied++;
2027
+ }
2028
+ }
2029
+ return { copied, modified };
2030
+ }
2031
+ async function ensureInstructionsMd(targetDir, config, existingLocation) {
2032
+ if (!config.instructionsFile) {
2033
+ return { created: false, updated: false, location: null, contentMerged: false };
2034
+ }
2035
+ const result = await ensureClaudeMd(targetDir, existingLocation);
2036
+ return {
2037
+ created: result.created,
2038
+ updated: result.updated,
2039
+ location: result.location === "dotclaude" ? "dotdir" : result.location,
2040
+ contentMerged: result.contentMerged
2041
+ };
2042
+ }
2043
+ async function getSyncStatus(targetDir) {
2044
+ const result = await readLockfile(targetDir);
2045
+ const agentsMdPath = path8.join(targetDir, "AGENTS.md");
2046
+ const skillsPath = path8.join(targetDir, ".claude", "skills");
2047
+ const [agentsMdExists, skillsExist] = await Promise.all([
2048
+ fs8.access(agentsMdPath).then(() => true).catch(() => false),
2049
+ fs8.access(skillsPath).then(() => true).catch(() => false)
2050
+ ]);
2051
+ return {
2052
+ hasSynced: result !== null,
2053
+ lockfile: result?.lockfile ?? null,
2054
+ agentsMdExists,
2055
+ skillsExist,
2056
+ schemaWarning: result?.schemaCompatibility.warning ?? null,
2057
+ schemaError: result?.schemaCompatibility.error ?? null
2058
+ };
2059
+ }
2060
+ function findOrphanedSkills(previousSkills, currentSkills) {
2061
+ return previousSkills.filter((skill) => !currentSkills.includes(skill));
2062
+ }
2063
+ async function deleteOrphanedSkills(targetDir, orphanedSkills, targets, previouslyTrackedSkills, options = {}) {
2064
+ const deleted = [];
2065
+ const skipped = [];
2066
+ const metadataOptions = options.metadataPrefix ? { metadataPrefix: options.metadataPrefix } : void 0;
2067
+ for (const skillName of orphanedSkills) {
2068
+ let wasDeleted = false;
2069
+ for (const target of targets) {
2070
+ const skillDir = path8.join(targetDir, `.${target}`, "skills", skillName);
2071
+ try {
2072
+ await fs8.access(skillDir);
2073
+ } catch {
2074
+ continue;
2075
+ }
2076
+ const skillMdPath = path8.join(skillDir, "SKILL.md");
2077
+ try {
2078
+ const content = await fs8.readFile(skillMdPath, "utf-8");
2079
+ const { isManaged, hasManualChanges } = await import("./skill-metadata-XXNMIXLD.js");
2080
+ if (!isManaged(content, metadataOptions)) {
2081
+ if (!skipped.includes(skillName)) {
2082
+ skipped.push(skillName);
2083
+ }
2084
+ continue;
2085
+ }
2086
+ const wasInPreviousLockfile = previouslyTrackedSkills.includes(skillName);
2087
+ const isUnmodified = !hasManualChanges(content, metadataOptions);
2088
+ if (!wasInPreviousLockfile && !isUnmodified) {
2089
+ if (!skipped.includes(skillName)) {
2090
+ skipped.push(skillName);
2091
+ }
2092
+ continue;
2093
+ }
2094
+ } catch {
2095
+ if (!skipped.includes(skillName)) {
2096
+ skipped.push(skillName);
2097
+ }
2098
+ continue;
2099
+ }
2100
+ await fs8.rm(skillDir, { recursive: true, force: true });
2101
+ wasDeleted = true;
2102
+ }
2103
+ if (wasDeleted && !deleted.includes(skillName)) {
2104
+ deleted.push(skillName);
2105
+ }
2106
+ }
2107
+ return { deleted, skipped };
2108
+ }
2109
+
2110
+ // src/commands/shared.ts
2111
+ import * as fs13 from "fs/promises";
2112
+ import * as path13 from "path";
2113
+ import * as prompts4 from "@clack/prompts";
2114
+ import pc6 from "picocolors";
2115
+
2116
+ // src/core/hooks.ts
2117
+ import * as fs9 from "fs/promises";
2118
+ import * as path9 from "path";
2119
+ var DEFAULT_CLI_NAME = "agent-conf";
2120
+ var DEFAULT_CONFIG_DIR = ".agent-conf";
2121
+ var DEFAULT_LOCKFILE_NAME = "lockfile.json";
2122
+ var HOOK_IDENTIFIER = "# agent-conf pre-commit hook";
2123
+ function generatePreCommitHook(config) {
2124
+ const { cliName, configDir, lockfileName } = config;
2125
+ return `#!/bin/bash
2126
+ # ${cliName} pre-commit hook
2127
+ # Prevents committing changes to managed files (skills and AGENTS.md global block)
2128
+ #
2129
+ # Installation:
2130
+ # Copy this file to .git/hooks/pre-commit
2131
+ # Or run: ${cliName} hooks install
2132
+
2133
+ set -e
2134
+
2135
+ # Get the repository root
2136
+ REPO_ROOT=$(git rev-parse --show-toplevel)
2137
+
2138
+ # Check if ${cliName} has been synced
2139
+ if [ ! -f "$REPO_ROOT/${configDir}/${lockfileName}" ]; then
2140
+ # Not synced, nothing to check
2141
+ exit 0
2142
+ fi
2143
+
2144
+ # Check if ${cliName} CLI is available
2145
+ if ! command -v ${cliName} &> /dev/null; then
2146
+ # CLI not installed, skip check
2147
+ exit 0
2148
+ fi
2149
+
2150
+ # Run ${cliName} check and capture output
2151
+ cd "$REPO_ROOT"
2152
+ CHECK_OUTPUT=$(${cliName} check 2>&1) || CHECK_EXIT=$?
2153
+ CHECK_EXIT=\${CHECK_EXIT:-0}
2154
+
2155
+ if [ $CHECK_EXIT -ne 0 ]; then
2156
+ echo ""
2157
+ echo "Error: Cannot commit changes to ${cliName}-managed files"
2158
+ echo ""
2159
+ echo "Output from '${cliName} check' command:"
2160
+ echo "$CHECK_OUTPUT"
2161
+ echo ""
2162
+ echo "Options:"
2163
+ echo " 1. Discard your changes: git checkout -- <file>"
2164
+ echo " 2. Skip this check: git commit --no-verify"
2165
+ echo " 3. Restore managed files: ${cliName} sync"
2166
+ echo " 4. For AGENTS.md: edit only the repo-specific block (between repo:start and repo:end)"
2167
+ echo " 5. For skills: create a new custom skill instead"
2168
+ echo ""
2169
+ exit 1
2170
+ fi
2171
+
2172
+ exit 0
2173
+ `;
2174
+ }
2175
+ function getHookConfig(config) {
2176
+ return {
2177
+ cliName: config?.cliName ?? DEFAULT_CLI_NAME,
2178
+ configDir: config?.configDir ?? DEFAULT_CONFIG_DIR,
2179
+ lockfileName: config?.lockfileName ?? DEFAULT_LOCKFILE_NAME
2180
+ };
2181
+ }
2182
+ async function installPreCommitHook(targetDir, config) {
2183
+ const hookConfig = getHookConfig(config);
2184
+ const hooksDir = path9.join(targetDir, ".git", "hooks");
2185
+ const hookPath = path9.join(hooksDir, "pre-commit");
2186
+ const hookContent = generatePreCommitHook(hookConfig);
2187
+ await fs9.mkdir(hooksDir, { recursive: true });
2188
+ let existingContent = null;
2189
+ try {
2190
+ existingContent = await fs9.readFile(hookPath, "utf-8");
2191
+ } catch {
2192
+ }
2193
+ if (existingContent !== null) {
2194
+ const isOurHook = existingContent.includes(HOOK_IDENTIFIER);
2195
+ if (!isOurHook) {
2196
+ return {
2197
+ installed: false,
2198
+ path: hookPath,
2199
+ alreadyExisted: true,
2200
+ wasUpdated: false
2201
+ };
2202
+ }
2203
+ if (existingContent === hookContent) {
2204
+ return {
2205
+ installed: true,
2206
+ path: hookPath,
2207
+ alreadyExisted: true,
2208
+ wasUpdated: false
2209
+ };
2210
+ }
2211
+ await fs9.writeFile(hookPath, hookContent, { mode: 493 });
2212
+ return {
2213
+ installed: true,
2214
+ path: hookPath,
2215
+ alreadyExisted: true,
2216
+ wasUpdated: true
2217
+ };
2218
+ }
2219
+ await fs9.writeFile(hookPath, hookContent, { mode: 493 });
2220
+ return {
2221
+ installed: true,
2222
+ path: hookPath,
2223
+ alreadyExisted: false,
2224
+ wasUpdated: false
2225
+ };
2226
+ }
2227
+
2228
+ // src/core/source.ts
2229
+ import { exec } from "child_process";
2230
+ import * as fs11 from "fs/promises";
2231
+ import * as path11 from "path";
2232
+ import { promisify } from "util";
2233
+ import { simpleGit as simpleGit2 } from "simple-git";
2234
+
2235
+ // src/config/loader.ts
2236
+ import * as fs10 from "fs/promises";
2237
+ import * as path10 from "path";
2238
+ import { parse as parseYaml } from "yaml";
2239
+ var CANONICAL_REPO_CONFIG = "agent-conf.yaml";
2240
+ async function loadCanonicalRepoConfig(basePath) {
2241
+ const configPath = path10.join(basePath, CANONICAL_REPO_CONFIG);
2242
+ try {
2243
+ const content = await fs10.readFile(configPath, "utf-8");
2244
+ const parsed = parseYaml(content);
2245
+ return CanonicalRepoConfigSchema.parse(parsed);
2246
+ } catch (error) {
2247
+ if (isNodeError(error) && error.code === "ENOENT") {
2248
+ return void 0;
2249
+ }
2250
+ throw new Error(`Failed to load ${CANONICAL_REPO_CONFIG}: ${error}`);
2251
+ }
2252
+ }
2253
+ function isNodeError(error) {
2254
+ return error instanceof Error && "code" in error;
2255
+ }
2256
+
2257
+ // src/core/source.ts
2258
+ var execAsync = promisify(exec);
2259
+ var DEFAULT_REF = "master";
2260
+ async function resolveLocalSource(options = {}) {
2261
+ let basePath;
2262
+ if (options.path) {
2263
+ basePath = path11.resolve(options.path);
2264
+ } else {
2265
+ basePath = await findCanonicalRepo(process.cwd());
2266
+ }
2267
+ await validateCanonicalRepo(basePath);
2268
+ const git = simpleGit2(basePath);
2269
+ let commitSha;
2270
+ try {
2271
+ const log2 = await git.log({ maxCount: 1 });
2272
+ commitSha = log2.latest?.hash;
2273
+ } catch {
2274
+ }
2275
+ const canonicalConfig = await loadCanonicalRepoConfig(basePath);
2276
+ const markerPrefix = canonicalConfig?.markers.prefix ?? "agent-conf";
2277
+ const rulesDir = canonicalConfig?.content.rules_dir;
2278
+ const source = {
2279
+ type: "local",
2280
+ path: basePath,
2281
+ commit_sha: commitSha
2282
+ };
2283
+ return {
2284
+ source,
2285
+ basePath,
2286
+ agentsMdPath: path11.join(basePath, "instructions", "AGENTS.md"),
2287
+ skillsPath: path11.join(basePath, "skills"),
2288
+ rulesPath: rulesDir ? path11.join(basePath, rulesDir) : null,
2289
+ markerPrefix
2290
+ };
2291
+ }
2292
+ async function resolveGithubSource(options, tempDir) {
2293
+ const { repository, ref = DEFAULT_REF } = options;
2294
+ await cloneRepository(repository, ref, tempDir);
2295
+ const clonedGit = simpleGit2(tempDir);
2296
+ const log2 = await clonedGit.log({ maxCount: 1 });
2297
+ const commitSha = log2.latest?.hash ?? "";
2298
+ const canonicalConfig = await loadCanonicalRepoConfig(tempDir);
2299
+ const markerPrefix = canonicalConfig?.markers.prefix ?? "agent-conf";
2300
+ const rulesDir = canonicalConfig?.content.rules_dir;
2301
+ const source = {
2302
+ type: "github",
2303
+ repository,
2304
+ commit_sha: commitSha,
2305
+ ref
2306
+ };
2307
+ return {
2308
+ source,
2309
+ basePath: tempDir,
2310
+ agentsMdPath: path11.join(tempDir, "instructions", "AGENTS.md"),
2311
+ skillsPath: path11.join(tempDir, "skills"),
2312
+ rulesPath: rulesDir ? path11.join(tempDir, rulesDir) : null,
2313
+ markerPrefix
2314
+ };
2315
+ }
2316
+ async function cloneRepository(repository, ref, tempDir) {
2317
+ if (await isGhAvailable()) {
2318
+ try {
2319
+ await execAsync(`gh repo clone ${repository} ${tempDir} -- --depth 1 --branch ${ref}`);
2320
+ return;
2321
+ } catch {
2322
+ }
2323
+ }
2324
+ const token = process.env.GITHUB_TOKEN;
2325
+ const repoUrl = token ? `https://x-access-token:${token}@github.com/${repository}.git` : `https://github.com/${repository}.git`;
2326
+ const git = simpleGit2();
2327
+ await git.clone(repoUrl, tempDir, ["--depth", "1", "--branch", ref]);
2328
+ }
2329
+ async function isGhAvailable() {
2330
+ try {
2331
+ await execAsync("gh --version");
2332
+ return true;
2333
+ } catch {
2334
+ return false;
2335
+ }
2336
+ }
2337
+ async function findCanonicalRepo(startDir) {
2338
+ let currentDir = path11.resolve(startDir);
2339
+ const root = path11.parse(currentDir).root;
2340
+ let checkDir = currentDir;
2341
+ while (checkDir !== root) {
2342
+ if (await isCanonicalRepo(checkDir)) {
2343
+ return checkDir;
2344
+ }
2345
+ checkDir = path11.dirname(checkDir);
2346
+ }
2347
+ currentDir = path11.resolve(startDir);
2348
+ while (currentDir !== root) {
2349
+ const parentDir = path11.dirname(currentDir);
2350
+ const siblingCanonicalRepo = path11.join(parentDir, "agent-conf");
2351
+ if (await isCanonicalRepo(siblingCanonicalRepo)) {
2352
+ return siblingCanonicalRepo;
2353
+ }
2354
+ currentDir = parentDir;
2355
+ }
2356
+ throw new Error(
2357
+ "Could not find canonical repository. Please specify --local <path> or run from within the canonical repo."
2358
+ );
2359
+ }
2360
+ async function isCanonicalRepo(dir) {
2361
+ try {
2362
+ const stat4 = await fs11.stat(dir).catch(() => null);
2363
+ if (!stat4?.isDirectory()) {
2364
+ return false;
2365
+ }
2366
+ const instructionsPath = path11.join(dir, "instructions", "AGENTS.md");
2367
+ const skillsPath = path11.join(dir, "skills");
2368
+ const [instructionsExists, skillsExists] = await Promise.all([
2369
+ fs11.access(instructionsPath).then(() => true).catch(() => false),
2370
+ fs11.access(skillsPath).then(() => true).catch(() => false)
2371
+ ]);
2372
+ if (!instructionsExists || !skillsExists) {
2373
+ return false;
2374
+ }
2375
+ try {
2376
+ const git = simpleGit2(dir);
2377
+ const remotes = await git.getRemotes(true);
2378
+ const hasMatchingRemote = remotes.some(
2379
+ (r) => r.refs.fetch?.includes("agent-conf") || r.refs.push?.includes("agent-conf")
2380
+ );
2381
+ if (hasMatchingRemote) {
2382
+ return true;
2383
+ }
2384
+ } catch {
2385
+ }
2386
+ return true;
2387
+ } catch {
2388
+ return false;
2389
+ }
2390
+ }
2391
+ async function validateCanonicalRepo(basePath) {
2392
+ const agentsMdPath = path11.join(basePath, "instructions", "AGENTS.md");
2393
+ const skillsPath = path11.join(basePath, "skills");
2394
+ const [agentsMdExists, skillsExists] = await Promise.all([
2395
+ fs11.access(agentsMdPath).then(() => true).catch(() => false),
2396
+ fs11.access(skillsPath).then(() => true).catch(() => false)
2397
+ ]);
2398
+ if (!agentsMdExists) {
2399
+ throw new Error(`Invalid canonical repository: missing instructions/AGENTS.md at ${basePath}`);
2400
+ }
2401
+ if (!skillsExists) {
2402
+ throw new Error(`Invalid canonical repository: missing skills/ directory at ${basePath}`);
2403
+ }
2404
+ }
2405
+ function formatSourceString(source) {
2406
+ if (source.type === "github") {
2407
+ const sha = source.commit_sha.slice(0, 7);
2408
+ return `github:${source.repository}@${sha}`;
2409
+ }
2410
+ if (source.commit_sha) {
2411
+ return `local:${source.path}@${source.commit_sha.slice(0, 7)}`;
2412
+ }
2413
+ return `local:${source.path}`;
2414
+ }
2415
+
2416
+ // src/core/version.ts
2417
+ import { execSync } from "child_process";
2418
+ var GITHUB_API_BASE = "https://api.github.com";
2419
+ function getGitHubToken() {
2420
+ if (process.env.GITHUB_TOKEN) {
2421
+ return process.env.GITHUB_TOKEN;
2422
+ }
2423
+ try {
2424
+ const token = execSync("gh auth token", {
2425
+ encoding: "utf-8",
2426
+ stdio: ["pipe", "pipe", "pipe"]
2427
+ }).trim();
2428
+ if (token) {
2429
+ return token;
2430
+ }
2431
+ } catch {
2432
+ }
2433
+ throw new Error(
2434
+ `GitHub authentication required to access agent-conf releases.
2435
+
2436
+ To fix this, do one of the following:
2437
+
2438
+ 1. Install and authenticate GitHub CLI (recommended):
2439
+ brew install gh
2440
+ gh auth login
2441
+
2442
+ 2. Set GITHUB_TOKEN environment variable:
2443
+ export GITHUB_TOKEN=<your-personal-access-token>
2444
+
2445
+ Create a token at https://github.com/settings/tokens
2446
+ with 'repo' scope for private repository access.`
2447
+ );
2448
+ }
2449
+ function getGitHubHeaders() {
2450
+ const token = getGitHubToken();
2451
+ return {
2452
+ Accept: "application/vnd.github.v3+json",
2453
+ "User-Agent": "agent-conf-cli",
2454
+ Authorization: `token ${token}`
2455
+ };
2456
+ }
2457
+ async function getLatestRelease(repo) {
2458
+ const url = `${GITHUB_API_BASE}/repos/${repo}/releases/latest`;
2459
+ const response = await fetch(url, {
2460
+ headers: getGitHubHeaders()
2461
+ });
2462
+ if (!response.ok) {
2463
+ if (response.status === 404) {
2464
+ throw new Error("No releases found for agent-conf repository");
2465
+ }
2466
+ throw new Error(`Failed to fetch latest release: ${response.statusText}`);
2467
+ }
2468
+ const data = await response.json();
2469
+ return parseReleaseResponse(data);
2470
+ }
2471
+ function parseReleaseResponse(data) {
2472
+ const tag = data.tag_name;
2473
+ return {
2474
+ tag,
2475
+ version: parseVersion(tag),
2476
+ commitSha: data.target_commitish || "",
2477
+ publishedAt: data.published_at,
2478
+ tarballUrl: data.tarball_url
2479
+ };
2480
+ }
2481
+ function parseVersion(tag) {
2482
+ return tag.startsWith("v") ? tag.slice(1) : tag;
2483
+ }
2484
+ function formatTag(version) {
2485
+ return version.startsWith("v") ? version : `v${version}`;
2486
+ }
2487
+ function isVersionRef(ref) {
2488
+ const normalized = ref.startsWith("v") ? ref.slice(1) : ref;
2489
+ return /^\d+\.\d+\.\d+(-[\w.]+)?$/.test(normalized);
2490
+ }
2491
+ function compareVersions(a, b) {
2492
+ const parseVer = (v) => {
2493
+ const clean = v.startsWith("v") ? v.slice(1) : v;
2494
+ const [main, prerelease] = clean.split("-");
2495
+ const parts = (main ?? "").split(".").map(Number);
2496
+ return { parts, prerelease };
2497
+ };
2498
+ const verA = parseVer(a);
2499
+ const verB = parseVer(b);
2500
+ for (let i = 0; i < 3; i++) {
2501
+ const partA = verA.parts[i] || 0;
2502
+ const partB = verB.parts[i] || 0;
2503
+ if (partA < partB) return -1;
2504
+ if (partA > partB) return 1;
2505
+ }
2506
+ if (verA.prerelease && !verB.prerelease) return -1;
2507
+ if (!verA.prerelease && verB.prerelease) return 1;
2508
+ if (verA.prerelease && verB.prerelease) {
2509
+ return verA.prerelease.localeCompare(verB.prerelease);
2510
+ }
2511
+ return 0;
2512
+ }
2513
+
2514
+ // src/core/workflows.ts
2515
+ import * as fs12 from "fs/promises";
2516
+ import * as path12 from "path";
2517
+ var DEFAULT_CLI_NAME2 = "agent-conf";
2518
+ var WORKFLOWS_DIR = ".github/workflows";
2519
+ function getWorkflowConfig(sourceRepo, config) {
2520
+ const name = config?.name ?? DEFAULT_CLI_NAME2;
2521
+ const secretName = `${name.toUpperCase().replace(/-/g, "_")}_TOKEN`;
2522
+ return {
2523
+ sourceRepo,
2524
+ cliName: config?.cliName ?? DEFAULT_CLI_NAME2,
2525
+ secretName,
2526
+ workflowPrefix: name
2527
+ };
2528
+ }
2529
+ function getWorkflowFiles(config) {
2530
+ const prefix = config.workflowPrefix;
2531
+ return [
2532
+ {
2533
+ name: "sync",
2534
+ filename: `${prefix}-sync.yml`,
2535
+ reusableWorkflow: "sync-reusable.yml"
2536
+ },
2537
+ {
2538
+ name: "check",
2539
+ filename: `${prefix}-check.yml`,
2540
+ reusableWorkflow: "check-reusable.yml"
2541
+ }
2542
+ ];
2543
+ }
2544
+ function getWorkflowsDir(repoRoot) {
2545
+ return path12.join(repoRoot, WORKFLOWS_DIR);
2546
+ }
2547
+ function getWorkflowPath(repoRoot, filename) {
2548
+ return path12.join(repoRoot, WORKFLOWS_DIR, filename);
2549
+ }
2550
+ function generateSyncWorkflow2(versionRef, config) {
2551
+ const { sourceRepo, cliName, secretName } = config;
2552
+ return `# ${cliName} Auto-Sync Workflow
2553
+ # Managed by ${cliName} CLI - do not edit the version ref manually
2554
+ #
2555
+ # This workflow syncs standards from the central repository.
2556
+ # Version changes should be made using: ${cliName} sync --ref <version>
2557
+ #
2558
+ # TOKEN: Requires a PAT with read access to the source repository.
2559
+ # Create a fine-grained PAT at https://github.com/settings/tokens?type=beta
2560
+ # with read access to ${sourceRepo}, then add it as ${secretName} secret.
2561
+
2562
+ name: ${cliName} Sync
2563
+
2564
+ on:
2565
+ schedule:
2566
+ - cron: '0 6 * * *' # Daily at 6am UTC
2567
+
2568
+ workflow_dispatch:
2569
+ inputs:
2570
+ force:
2571
+ description: 'Force sync even if no updates detected'
2572
+ required: false
2573
+ default: false
2574
+ type: boolean
2575
+
2576
+ repository_dispatch:
2577
+ types: [${cliName.replace(/-/g, "_")}-release]
2578
+
2579
+ concurrency:
2580
+ group: ${cliName}-sync
2581
+ cancel-in-progress: false
2582
+
2583
+ jobs:
2584
+ sync:
2585
+ uses: ${sourceRepo}/.github/workflows/sync-reusable.yml@${versionRef}
2586
+ with:
2587
+ force: \${{ inputs.force || false }}
2588
+ reviewers: \${{ vars.${secretName.replace(/_TOKEN$/, "_REVIEWERS")} || '' }}
2589
+ secrets:
2590
+ token: \${{ secrets.${secretName} }}
2591
+ `;
2592
+ }
2593
+ function generateCheckWorkflow2(versionRef, config) {
2594
+ const { sourceRepo, cliName } = config;
2595
+ return `# ${cliName} File Integrity Check
2596
+ # Managed by ${cliName} CLI - do not edit the version ref manually
2597
+ #
2598
+ # This workflow checks that ${cliName}-managed files haven't been modified.
2599
+ # Version changes should be made using: ${cliName} sync --ref <version>
2600
+
2601
+ name: ${cliName} Check
2602
+
2603
+ on:
2604
+ pull_request:
2605
+ paths:
2606
+ - '.claude/skills/**'
2607
+ - '.codex/skills/**'
2608
+ - 'AGENTS.md'
2609
+ push:
2610
+ paths:
2611
+ - '.claude/skills/**'
2612
+ - '.codex/skills/**'
2613
+ - 'AGENTS.md'
2614
+
2615
+ jobs:
2616
+ check:
2617
+ uses: ${sourceRepo}/.github/workflows/check-reusable.yml@${versionRef}
2618
+ `;
2619
+ }
2620
+ function generateWorkflow(workflow, versionRef, config) {
2621
+ switch (workflow.name) {
2622
+ case "sync":
2623
+ return generateSyncWorkflow2(versionRef, config);
2624
+ case "check":
2625
+ return generateCheckWorkflow2(versionRef, config);
2626
+ default:
2627
+ throw new Error(`Unknown workflow: ${workflow.name}`);
2628
+ }
2629
+ }
2630
+ async function ensureWorkflowsDir(repoRoot) {
2631
+ const dir = getWorkflowsDir(repoRoot);
2632
+ await fs12.mkdir(dir, { recursive: true });
2633
+ }
2634
+ async function writeWorkflow(repoRoot, workflow, versionRef, config) {
2635
+ await ensureWorkflowsDir(repoRoot);
2636
+ const filePath = getWorkflowPath(repoRoot, workflow.filename);
2637
+ const content = generateWorkflow(workflow, versionRef, config);
2638
+ await fs12.writeFile(filePath, content, "utf-8");
2639
+ }
2640
+ async function syncWorkflows(repoRoot, versionRef, sourceRepo, resolvedConfig) {
2641
+ const config = getWorkflowConfig(sourceRepo, resolvedConfig);
2642
+ const workflowFiles = getWorkflowFiles(config);
2643
+ const result = {
2644
+ created: [],
2645
+ updated: [],
2646
+ unchanged: []
2647
+ };
2648
+ for (const workflow of workflowFiles) {
2649
+ const filePath = getWorkflowPath(repoRoot, workflow.filename);
2650
+ const expectedContent = generateWorkflow(workflow, versionRef, config);
2651
+ let existingContent = null;
2652
+ try {
2653
+ existingContent = await fs12.readFile(filePath, "utf-8");
2654
+ } catch {
2655
+ }
2656
+ if (existingContent === null) {
2657
+ await writeWorkflow(repoRoot, workflow, versionRef, config);
2658
+ result.created.push(workflow.filename);
2659
+ } else if (existingContent !== expectedContent) {
2660
+ await writeWorkflow(repoRoot, workflow, versionRef, config);
2661
+ result.updated.push(workflow.filename);
2662
+ } else {
2663
+ result.unchanged.push(workflow.filename);
2664
+ }
2665
+ }
2666
+ return result;
2667
+ }
2668
+
2669
+ // src/commands/shared.ts
2670
+ async function parseAndValidateTargets(targetOptions) {
2671
+ const logger = createLogger();
2672
+ try {
2673
+ return parseTargets(targetOptions ?? ["claude"]);
2674
+ } catch (error) {
2675
+ logger.error(error instanceof Error ? error.message : String(error));
2676
+ logger.info(`Supported targets: ${SUPPORTED_TARGETS.join(", ")}`);
2677
+ process.exit(1);
2678
+ }
2679
+ }
2680
+ async function resolveTargetDirectory() {
2681
+ const logger = createLogger();
2682
+ const cwd = process.cwd();
2683
+ const gitRoot = await getGitRoot(cwd);
2684
+ if (!gitRoot) {
2685
+ logger.error(
2686
+ "Not inside a git repository. Please run this command from within a git repository."
2687
+ );
2688
+ process.exit(1);
2689
+ }
2690
+ if (gitRoot !== cwd) {
2691
+ logger.info(`Syncing to repository root: ${formatPath(gitRoot)}`);
2692
+ }
2693
+ return gitRoot;
2694
+ }
2695
+ async function resolveVersion(options, status, _commandName, repo) {
2696
+ const logger = createLogger();
2697
+ if (options.local !== void 0) {
2698
+ return {
2699
+ ref: "local",
2700
+ version: void 0,
2701
+ isRelease: false,
2702
+ releaseInfo: null
2703
+ };
2704
+ }
2705
+ if (options.ref) {
2706
+ if (isVersionRef(options.ref)) {
2707
+ return {
2708
+ ref: formatTag(options.ref),
2709
+ version: parseVersion(options.ref),
2710
+ isRelease: true,
2711
+ releaseInfo: null
2712
+ };
2713
+ }
2714
+ return {
2715
+ ref: options.ref,
2716
+ version: void 0,
2717
+ isRelease: false,
2718
+ releaseInfo: null
2719
+ };
2720
+ }
2721
+ if (options.pinned) {
2722
+ if (!status.lockfile?.pinned_version) {
2723
+ logger.error("Cannot use --pinned: no version pinned in lockfile.");
2724
+ process.exit(1);
2725
+ }
2726
+ const version = status.lockfile.pinned_version;
2727
+ return {
2728
+ ref: formatTag(version),
2729
+ version,
2730
+ isRelease: true,
2731
+ releaseInfo: null
2732
+ };
2733
+ }
2734
+ if (!repo) {
2735
+ logger.warn("No source repository specified. Using master branch.");
2736
+ return {
2737
+ ref: "master",
2738
+ version: void 0,
2739
+ isRelease: false,
2740
+ releaseInfo: null
2741
+ };
2742
+ }
2743
+ const spinner = logger.spinner("Fetching latest release...");
2744
+ spinner.start();
2745
+ try {
2746
+ const release = await getLatestRelease(repo);
2747
+ spinner.succeed(`Latest release: ${release.tag}`);
2748
+ return {
2749
+ ref: release.tag,
2750
+ version: release.version,
2751
+ isRelease: true,
2752
+ releaseInfo: release
2753
+ };
2754
+ } catch {
2755
+ spinner.info("No releases found, using master branch");
2756
+ return {
2757
+ ref: "master",
2758
+ version: void 0,
2759
+ isRelease: false,
2760
+ releaseInfo: null
2761
+ };
2762
+ }
2763
+ }
2764
+ async function resolveSource(options, resolvedVersion) {
2765
+ const logger = createLogger();
2766
+ let resolvedSource;
2767
+ let tempDir = null;
2768
+ const spinner = logger.spinner("Resolving source...");
2769
+ try {
2770
+ if (options.local !== void 0) {
2771
+ spinner.start();
2772
+ const localSourceOptions = typeof options.local === "string" ? { path: resolvePath(options.local) } : {};
2773
+ resolvedSource = await resolveLocalSource(localSourceOptions);
2774
+ spinner.succeed(`Using local source: ${formatPath(resolvedSource.basePath)}`);
2775
+ return { resolvedSource, tempDir, repository: "" };
2776
+ }
2777
+ const repository = options.source;
2778
+ if (!repository) {
2779
+ spinner.fail("No canonical source specified");
2780
+ logger.error(`No canonical source specified.
2781
+
2782
+ Specify a source using one of these methods:
2783
+ 1. CLI flag: agent-conf init --source acme/engineering-standards
2784
+ 2. Config file: Add 'source.repository' to .agent-conf.yaml
2785
+
2786
+ Example .agent-conf.yaml:
2787
+ source:
2788
+ type: github
2789
+ repository: acme/engineering-standards`);
2790
+ process.exit(1);
2791
+ }
2792
+ spinner.start();
2793
+ tempDir = await createTempDir();
2794
+ const ref = resolvedVersion.ref;
2795
+ spinner.text = `Cloning ${repository}@${ref}...`;
2796
+ resolvedSource = await resolveGithubSource({ repository, ref }, tempDir);
2797
+ spinner.succeed(`Cloned from GitHub: ${formatSourceString(resolvedSource.source)}`);
2798
+ return { resolvedSource, tempDir, repository };
2799
+ } catch (error) {
2800
+ spinner.fail("Failed to resolve source");
2801
+ logger.error(error instanceof Error ? error.message : String(error));
2802
+ if (tempDir) await removeTempDir(tempDir);
2803
+ process.exit(1);
2804
+ }
2805
+ }
2806
+ async function promptMergeOrOverride(status, options, tempDir) {
2807
+ let shouldOverride = options.override ?? false;
2808
+ if (status.hasSynced) {
2809
+ return shouldOverride;
2810
+ }
2811
+ if (status.agentsMdExists && !options.yes && !options.override) {
2812
+ const action = await prompts4.select({
2813
+ message: "An AGENTS.md file already exists. How would you like to proceed?",
2814
+ options: [
2815
+ {
2816
+ value: "merge",
2817
+ label: "Merge (recommended)",
2818
+ hint: "Preserves your existing content in a repository-specific block"
2819
+ },
2820
+ {
2821
+ value: "override",
2822
+ label: "Override",
2823
+ hint: "Replaces everything with fresh global standards"
2824
+ }
2825
+ ]
2826
+ });
2827
+ if (prompts4.isCancel(action)) {
2828
+ prompts4.cancel("Operation cancelled");
2829
+ if (tempDir) await removeTempDir(tempDir);
2830
+ process.exit(0);
2831
+ }
2832
+ shouldOverride = action === "override";
2833
+ }
2834
+ return shouldOverride;
2835
+ }
2836
+ async function checkModifiedFilesBeforeSync(targetDir, targets, options, tempDir) {
2837
+ const modifiedFiles = await getModifiedManagedFiles(targetDir, targets);
2838
+ if (modifiedFiles.length === 0) {
2839
+ return true;
2840
+ }
2841
+ const logger = createLogger();
2842
+ console.log();
2843
+ console.log(pc6.yellow(`\u26A0 ${modifiedFiles.length} managed file(s) have been manually modified:`));
2844
+ for (const file of modifiedFiles) {
2845
+ const label = file.type === "agents" ? "(global block)" : "";
2846
+ console.log(` ${pc6.yellow("~")} ${file.path} ${pc6.dim(label)}`);
2847
+ }
2848
+ console.log();
2849
+ if (options.yes) {
2850
+ logger.warn("Proceeding with sync (--yes flag). Modified files will be overwritten.");
2851
+ return true;
2852
+ }
2853
+ const proceed = await prompts4.confirm({
2854
+ message: "These files will be overwritten. Continue with sync?",
2855
+ initialValue: false
2856
+ });
2857
+ if (prompts4.isCancel(proceed) || !proceed) {
2858
+ prompts4.cancel("Sync cancelled. Your modified files were preserved.");
2859
+ if (tempDir) await removeTempDir(tempDir);
2860
+ process.exit(0);
2861
+ }
2862
+ return true;
2863
+ }
2864
+ async function performSync(options) {
2865
+ const { targetDir, resolvedSource, resolvedVersion, shouldOverride, targets, context, tempDir } = options;
2866
+ const logger = createLogger();
2867
+ const previousLockfileResult = await readLockfile(targetDir);
2868
+ const previousSkills = previousLockfileResult?.lockfile.content.skills ?? [];
2869
+ const previousRules = previousLockfileResult?.lockfile.content.rules?.files ?? [];
2870
+ const previousTargets = previousLockfileResult?.lockfile.content.targets ?? ["claude"];
2871
+ const syncSpinner = logger.spinner("Syncing...");
2872
+ syncSpinner.start();
2873
+ try {
2874
+ const syncOptions = {
2875
+ override: shouldOverride,
2876
+ targets
2877
+ };
2878
+ if (resolvedVersion.version) {
2879
+ syncOptions.pinnedVersion = resolvedVersion.version;
2880
+ }
2881
+ const result = await sync(targetDir, resolvedSource, syncOptions);
2882
+ syncSpinner.stop();
2883
+ const orphanedSkills = findOrphanedSkills(previousSkills, result.skills.synced);
2884
+ let orphanResult = { deleted: [], skipped: [] };
2885
+ if (orphanedSkills.length > 0) {
2886
+ const allTargets = [.../* @__PURE__ */ new Set([...previousTargets, ...targets])];
2887
+ if (options.yes) {
2888
+ orphanResult = await deleteOrphanedSkills(
2889
+ targetDir,
2890
+ orphanedSkills,
2891
+ allTargets,
2892
+ previousSkills
2893
+ );
2894
+ } else {
2895
+ console.log();
2896
+ console.log(
2897
+ pc6.yellow(
2898
+ `\u26A0 ${orphanedSkills.length} skill(s) were previously synced but are no longer in the source:`
2899
+ )
2900
+ );
2901
+ for (const skill of orphanedSkills) {
2902
+ console.log(` ${pc6.yellow("\xB7")} ${skill}`);
2903
+ }
2904
+ console.log();
2905
+ const deleteOrphans = await prompts4.confirm({
2906
+ message: "Delete these orphaned skills?",
2907
+ initialValue: true
2908
+ });
2909
+ if (prompts4.isCancel(deleteOrphans)) {
2910
+ logger.info("Skipping orphan deletion.");
2911
+ } else if (deleteOrphans) {
2912
+ orphanResult = await deleteOrphanedSkills(
2913
+ targetDir,
2914
+ orphanedSkills,
2915
+ allTargets,
2916
+ previousSkills
2917
+ );
2918
+ }
2919
+ }
2920
+ }
2921
+ if (result.skills.validationErrors.length > 0) {
2922
+ console.log();
2923
+ console.log(
2924
+ pc6.yellow(`\u26A0 ${result.skills.validationErrors.length} skill(s) have invalid frontmatter:`)
2925
+ );
2926
+ for (const error of result.skills.validationErrors) {
2927
+ console.log(` ${pc6.yellow("!")} ${error.skillName}/SKILL.md`);
2928
+ for (const msg of error.errors) {
2929
+ console.log(` ${pc6.dim("-")} ${msg}`);
2930
+ }
2931
+ }
2932
+ console.log();
2933
+ console.log(pc6.dim("Skills must have frontmatter with 'name' and 'description' fields."));
2934
+ }
2935
+ let workflowResult = null;
2936
+ if (resolvedVersion.ref !== "local" && options.sourceRepo) {
2937
+ const workflowSpinner = logger.spinner("Syncing workflow files...");
2938
+ workflowSpinner.start();
2939
+ const workflowRef = resolvedVersion.isRelease && resolvedVersion.version ? formatTag(resolvedVersion.version) : resolvedVersion.ref;
2940
+ workflowResult = await syncWorkflows(targetDir, workflowRef, options.sourceRepo);
2941
+ workflowSpinner.stop();
2942
+ }
2943
+ const hookResult = await installPreCommitHook(targetDir);
2944
+ const summaryLines = [];
2945
+ console.log();
2946
+ console.log(pc6.bold("Sync complete:"));
2947
+ console.log();
2948
+ const agentsMdPath = formatPath(path13.join(targetDir, "AGENTS.md"));
2949
+ if (result.agentsMd.merged) {
2950
+ const label = context.commandName === "sync" ? "(updated)" : "(merged)";
2951
+ console.log(` ${pc6.green("+")} ${agentsMdPath} ${pc6.dim(label)}`);
2952
+ summaryLines.push(`- \`AGENTS.md\` ${label}`);
2953
+ } else {
2954
+ console.log(` ${pc6.green("+")} ${agentsMdPath} ${pc6.dim("(created)")}`);
2955
+ summaryLines.push("- `AGENTS.md` (created)");
2956
+ }
2957
+ for (const targetResult of result.targets) {
2958
+ const config = getTargetConfig(targetResult.target);
2959
+ if (config.instructionsFile) {
2960
+ const instructionsPath = targetResult.instructionsMd.location === "root" ? formatPath(path13.join(targetDir, config.instructionsFile)) : formatPath(path13.join(targetDir, config.dir, config.instructionsFile));
2961
+ const instructionsRelPath = targetResult.instructionsMd.location === "root" ? config.instructionsFile : `${config.dir}/${config.instructionsFile}`;
2962
+ if (targetResult.instructionsMd.created) {
2963
+ console.log(` ${pc6.green("+")} ${instructionsPath} ${pc6.dim("(created)")}`);
2964
+ summaryLines.push(`- \`${instructionsRelPath}\` (created)`);
2965
+ } else if (targetResult.instructionsMd.updated) {
2966
+ const hint = targetResult.instructionsMd.contentMerged ? "(content merged into AGENTS.md, reference added)" : "(reference added)";
2967
+ console.log(` ${pc6.yellow("~")} ${instructionsPath} ${pc6.dim(hint)}`);
2968
+ summaryLines.push(`- \`${instructionsRelPath}\` ${hint}`);
2969
+ } else {
2970
+ console.log(` ${pc6.dim("-")} ${instructionsPath} ${pc6.dim("(unchanged)")}`);
2971
+ summaryLines.push(`- \`${instructionsRelPath}\` (unchanged)`);
2972
+ }
2973
+ }
2974
+ const skillsPath = formatPath(path13.join(targetDir, config.dir, "skills"));
2975
+ const skillsRelPath = `${config.dir}/skills/`;
2976
+ const newSkills = result.skills.synced.filter((s) => !previousSkills.includes(s)).sort();
2977
+ const updatedSkills = result.skills.modified.filter((s) => previousSkills.includes(s)).sort();
2978
+ const removedSkills = orphanResult.deleted.sort();
2979
+ const skillsHadChanges = newSkills.length > 0 || updatedSkills.length > 0 || removedSkills.length > 0;
2980
+ const skillsStatusIcon = skillsHadChanges ? pc6.green("+") : pc6.dim("-");
2981
+ const skillsStatusLabel = skillsHadChanges ? "(updated)" : "(unchanged)";
2982
+ console.log(
2983
+ ` ${skillsStatusIcon} ${skillsPath}/ ${pc6.dim(`(total: ${result.skills.synced.length} skills, ${targetResult.skills.copied} files) ${skillsStatusLabel}`)}`
2984
+ );
2985
+ summaryLines.push(
2986
+ `- \`${skillsRelPath}\` (total: ${result.skills.synced.length} skills, ${targetResult.skills.copied} files) ${skillsStatusLabel}`
2987
+ );
2988
+ const MAX_ITEMS_DEFAULT = 5;
2989
+ const shouldExpand = options.expandChanges === true;
2990
+ const formatSkillList = (skills, icon, colorFn, label, mdLabel) => {
2991
+ if (skills.length === 0) return;
2992
+ const maxDisplay = shouldExpand ? skills.length : MAX_ITEMS_DEFAULT;
2993
+ const displaySkills = skills.slice(0, maxDisplay);
2994
+ const hiddenCount = skills.length - displaySkills.length;
2995
+ for (const skill of displaySkills) {
2996
+ const skillPath = formatPath(path13.join(targetDir, config.dir, "skills", skill));
2997
+ const skillRelPath = `${config.dir}/skills/${skill}/`;
2998
+ console.log(` ${colorFn(icon)} ${skillPath}/ ${pc6.dim(`(${label})`)}`);
2999
+ summaryLines.push(` - \`${skillRelPath}\` (${mdLabel})`);
3000
+ }
3001
+ if (hiddenCount > 0) {
3002
+ console.log(` ${pc6.dim(` ... ${hiddenCount} more ${label}`)}`);
3003
+ summaryLines.push(` - ... ${hiddenCount} more ${mdLabel}`);
3004
+ }
3005
+ };
3006
+ formatSkillList(newSkills, "+", pc6.green, "new", "new");
3007
+ formatSkillList(updatedSkills, "~", pc6.yellow, "updated", "updated");
3008
+ for (const skill of removedSkills) {
3009
+ const orphanPath = formatPath(path13.join(targetDir, config.dir, "skills", skill));
3010
+ const orphanRelPath = `${config.dir}/skills/${skill}/`;
3011
+ console.log(` ${pc6.red("-")} ${orphanPath}/ ${pc6.dim("(removed)")}`);
3012
+ summaryLines.push(` - \`${orphanRelPath}\` (removed)`);
3013
+ }
3014
+ if (orphanResult.skipped.length > 0) {
3015
+ for (const skill of orphanResult.skipped) {
3016
+ const orphanPath = formatPath(path13.join(targetDir, config.dir, "skills", skill));
3017
+ const orphanRelPath = `${config.dir}/skills/${skill}/`;
3018
+ console.log(` ${pc6.yellow("!")} ${orphanPath}/ ${pc6.dim("(orphaned but skipped)")}`);
3019
+ summaryLines.push(` - \`${orphanRelPath}\` (orphaned but skipped)`);
3020
+ }
3021
+ }
3022
+ if (result.rules && result.rules.claudeFiles.length > 0 && targetResult.target === "claude") {
3023
+ const rulesPath = formatPath(path13.join(targetDir, config.dir, "rules"));
3024
+ const rulesRelPath = `${config.dir}/rules/`;
3025
+ const rulesCount = result.rules.claudeFiles.length;
3026
+ const newRules = result.rules.synced.filter((r) => !previousRules.includes(r)).sort();
3027
+ const updatedRules = result.rules.modified.filter((r) => previousRules.includes(r)).sort();
3028
+ const rulesHadChanges = newRules.length > 0 || updatedRules.length > 0;
3029
+ const rulesStatusIcon = rulesHadChanges ? pc6.green("+") : pc6.dim("-");
3030
+ const rulesStatusLabel = rulesHadChanges ? "(updated)" : "(unchanged)";
3031
+ console.log(
3032
+ ` ${rulesStatusIcon} ${rulesPath}/ ${pc6.dim(`(total: ${rulesCount} rules) ${rulesStatusLabel}`)}`
3033
+ );
3034
+ summaryLines.push(`- \`${rulesRelPath}\` (total: ${rulesCount} rules) ${rulesStatusLabel}`);
3035
+ const formatRuleList = (rules, icon, colorFn, label, mdLabel) => {
3036
+ if (rules.length === 0) return;
3037
+ const maxDisplay = shouldExpand ? rules.length : MAX_ITEMS_DEFAULT;
3038
+ const displayRules = rules.slice(0, maxDisplay);
3039
+ const hiddenCount = rules.length - displayRules.length;
3040
+ for (const rule of displayRules) {
3041
+ const rulePath = formatPath(path13.join(targetDir, config.dir, "rules", rule));
3042
+ const ruleRelPath = `${config.dir}/rules/${rule}`;
3043
+ console.log(` ${colorFn(icon)} ${rulePath} ${pc6.dim(`(${label})`)}`);
3044
+ summaryLines.push(` - \`${ruleRelPath}\` (${mdLabel})`);
3045
+ }
3046
+ if (hiddenCount > 0) {
3047
+ console.log(` ${pc6.dim(` ... ${hiddenCount} more ${label}`)}`);
3048
+ summaryLines.push(` - ... ${hiddenCount} more ${mdLabel}`);
3049
+ }
3050
+ };
3051
+ formatRuleList(newRules, "+", pc6.green, "new", "new");
3052
+ formatRuleList(updatedRules, "~", pc6.yellow, "updated", "updated");
3053
+ }
3054
+ if (result.rules?.codexUpdated && targetResult.target === "codex") {
3055
+ const rulesCount = result.rules.synced.length;
3056
+ console.log(
3057
+ ` ${pc6.green("+")} ${pc6.dim("AGENTS.md rules section")} ${pc6.dim(`(${rulesCount} rules concatenated)`)}`
3058
+ );
3059
+ summaryLines.push(`- AGENTS.md rules section (${rulesCount} rules concatenated)`);
3060
+ const newRules = result.rules.synced.filter((r) => !previousRules.includes(r)).sort();
3061
+ const updatedRules = result.rules.modified.filter((r) => previousRules.includes(r)).sort();
3062
+ const formatCodexRuleList = (rules, icon, colorFn, label, mdLabel) => {
3063
+ if (rules.length === 0) return;
3064
+ const maxDisplay = shouldExpand ? rules.length : MAX_ITEMS_DEFAULT;
3065
+ const displayRules = rules.slice(0, maxDisplay);
3066
+ const hiddenCount = rules.length - displayRules.length;
3067
+ for (const rule of displayRules) {
3068
+ console.log(` ${colorFn(icon)} ${rule} ${pc6.dim(`(${label})`)}`);
3069
+ summaryLines.push(` - \`${rule}\` (${mdLabel})`);
3070
+ }
3071
+ if (hiddenCount > 0) {
3072
+ console.log(` ${pc6.dim(` ... ${hiddenCount} more ${label}`)}`);
3073
+ summaryLines.push(` - ... ${hiddenCount} more ${mdLabel}`);
3074
+ }
3075
+ };
3076
+ formatCodexRuleList(newRules, "+", pc6.green, "new", "new");
3077
+ formatCodexRuleList(updatedRules, "~", pc6.yellow, "updated", "updated");
3078
+ }
3079
+ }
3080
+ if (workflowResult) {
3081
+ for (const filename of workflowResult.created) {
3082
+ const workflowPath = formatPath(path13.join(targetDir, ".github/workflows", filename));
3083
+ console.log(` ${pc6.green("+")} ${workflowPath} ${pc6.dim("(created)")}`);
3084
+ summaryLines.push(`- \`.github/workflows/${filename}\` (created)`);
3085
+ }
3086
+ for (const filename of workflowResult.updated) {
3087
+ const workflowPath = formatPath(path13.join(targetDir, ".github/workflows", filename));
3088
+ console.log(` ${pc6.yellow("~")} ${workflowPath} ${pc6.dim("(updated)")}`);
3089
+ summaryLines.push(`- \`.github/workflows/${filename}\` (updated)`);
3090
+ }
3091
+ for (const filename of workflowResult.unchanged) {
3092
+ const workflowPath = formatPath(path13.join(targetDir, ".github/workflows", filename));
3093
+ console.log(` ${pc6.dim("-")} ${workflowPath} ${pc6.dim("(unchanged)")}`);
3094
+ summaryLines.push(`- \`.github/workflows/${filename}\` (unchanged)`);
3095
+ }
3096
+ }
3097
+ const lockfilePath = formatPath(path13.join(targetDir, ".agent-conf", "agent-conf.lock"));
3098
+ console.log(` ${pc6.green("+")} ${lockfilePath}`);
3099
+ summaryLines.push("- `.agent-conf/lockfile.json` (updated)");
3100
+ const hookPath = formatPath(path13.join(targetDir, ".git/hooks/pre-commit"));
3101
+ if (hookResult.installed) {
3102
+ if (hookResult.alreadyExisted && !hookResult.wasUpdated) {
3103
+ console.log(` ${pc6.dim("-")} ${hookPath} ${pc6.dim("(unchanged)")}`);
3104
+ summaryLines.push("- `.git/hooks/pre-commit` (unchanged)");
3105
+ } else if (hookResult.wasUpdated) {
3106
+ console.log(` ${pc6.yellow("~")} ${hookPath} ${pc6.dim("(updated)")}`);
3107
+ summaryLines.push("- `.git/hooks/pre-commit` (updated)");
3108
+ } else {
3109
+ console.log(` ${pc6.green("+")} ${hookPath} ${pc6.dim("(installed)")}`);
3110
+ summaryLines.push("- `.git/hooks/pre-commit` (installed)");
3111
+ }
3112
+ } else if (hookResult.alreadyExisted) {
3113
+ console.log(` ${pc6.yellow("!")} ${hookPath} ${pc6.dim("(skipped - custom hook exists)")}`);
3114
+ summaryLines.push("- `.git/hooks/pre-commit` (skipped - custom hook exists)");
3115
+ }
3116
+ console.log();
3117
+ console.log(pc6.dim(`Source: ${formatSourceString(resolvedSource.source)}`));
3118
+ if (resolvedVersion.version) {
3119
+ console.log(pc6.dim(`Version: ${resolvedVersion.version}`));
3120
+ }
3121
+ if (targets.length > 1) {
3122
+ console.log(pc6.dim(`Targets: ${targets.join(", ")}`));
3123
+ }
3124
+ if (options.summaryFile) {
3125
+ const sourceStr = formatSourceString(resolvedSource.source);
3126
+ const versionStr = resolvedVersion.version ? `v${resolvedVersion.version}` : resolvedVersion.ref;
3127
+ const summary = `## Changes
3128
+
3129
+ ${summaryLines.join("\n")}
3130
+
3131
+ ---
3132
+ **Source:** ${sourceStr}
3133
+ **Version:** ${versionStr}
3134
+ `;
3135
+ await fs13.writeFile(options.summaryFile, summary, "utf-8");
3136
+ }
3137
+ prompts4.outro(pc6.green("Done!"));
3138
+ } catch (error) {
3139
+ syncSpinner.fail("Sync failed");
3140
+ logger.error(error instanceof Error ? error.message : String(error));
3141
+ process.exit(1);
3142
+ } finally {
3143
+ if (tempDir) await removeTempDir(tempDir);
3144
+ }
3145
+ }
3146
+
3147
+ // src/commands/init.ts
3148
+ async function initCommand(options) {
3149
+ const logger = createLogger();
3150
+ console.log();
3151
+ prompts5.intro(pc7.bold("agent-conf init"));
3152
+ const targetDir = await resolveTargetDirectory();
3153
+ const targets = await parseAndValidateTargets(options.target);
3154
+ const status = await getSyncStatus(targetDir);
3155
+ if (status.schemaError) {
3156
+ logger.error(status.schemaError);
3157
+ process.exit(1);
3158
+ }
3159
+ if (status.schemaWarning) {
3160
+ logger.warn(status.schemaWarning);
3161
+ }
3162
+ if (status.hasSynced && !options.yes) {
3163
+ const shouldContinue = await prompts5.confirm({
3164
+ message: "This repository has already been synced. Do you want to sync again?"
3165
+ });
3166
+ if (prompts5.isCancel(shouldContinue) || !shouldContinue) {
3167
+ prompts5.cancel("Operation cancelled");
3168
+ process.exit(0);
3169
+ }
3170
+ }
3171
+ const resolvedVersion = await resolveVersion(options, status, "init", options.source);
3172
+ const { resolvedSource, tempDir, repository } = await resolveSource(options, resolvedVersion);
3173
+ const shouldOverride = await promptMergeOrOverride(status, options, tempDir);
3174
+ await checkModifiedFilesBeforeSync(targetDir, targets, options, tempDir);
3175
+ await performSync({
3176
+ targetDir,
3177
+ resolvedSource,
3178
+ resolvedVersion,
3179
+ shouldOverride,
3180
+ targets,
3181
+ context: {
3182
+ commandName: "init",
3183
+ status
3184
+ },
3185
+ tempDir,
3186
+ yes: options.yes,
3187
+ sourceRepo: repository
3188
+ });
3189
+ if (!options.yes) {
3190
+ await promptCompletionInstall();
3191
+ }
3192
+ }
3193
+
3194
+ // src/commands/status.ts
3195
+ import * as path14 from "path";
3196
+ import pc8 from "picocolors";
3197
+ async function statusCommand(options = {}) {
3198
+ const targetDir = process.cwd();
3199
+ const status = await getSyncStatus(targetDir);
3200
+ console.log();
3201
+ console.log(pc8.bold("agent-conf sync status"));
3202
+ console.log();
3203
+ if (!status.hasSynced) {
3204
+ console.log(pc8.yellow("Not synced"));
3205
+ console.log();
3206
+ console.log(pc8.dim("Run `agent-conf init` to sync engineering standards to this repository."));
3207
+ console.log();
3208
+ return;
3209
+ }
3210
+ const lockfile = status.lockfile;
3211
+ console.log(`${pc8.green("Synced")}`);
3212
+ console.log();
3213
+ console.log(pc8.bold("Source:"));
3214
+ console.log(` ${formatSourceString(lockfile.source)}`);
3215
+ console.log();
3216
+ console.log(pc8.bold("Last synced:"));
3217
+ const syncedAt = new Date(lockfile.synced_at);
3218
+ console.log(` ${syncedAt.toLocaleString()}`);
3219
+ console.log();
3220
+ console.log(pc8.bold("Content:"));
3221
+ console.log(` AGENTS.md: ${status.agentsMdExists ? pc8.green("present") : pc8.red("missing")}`);
3222
+ console.log(` Skills: ${lockfile.content.skills.length} synced`);
3223
+ if (lockfile.content.skills.length > 0) {
3224
+ for (const skill of lockfile.content.skills) {
3225
+ console.log(` - ${skill}`);
3226
+ }
3227
+ }
3228
+ console.log();
3229
+ if (options.check) {
3230
+ const targets = lockfile.content.targets ?? ["claude"];
3231
+ const allFiles = await checkAllManagedFiles(targetDir, targets);
3232
+ const modifiedFiles = allFiles.filter((f) => f.hasChanges);
3233
+ console.log(pc8.bold("File integrity:"));
3234
+ if (modifiedFiles.length === 0) {
3235
+ console.log(` ${pc8.green("\u2713")} All managed files are unchanged`);
3236
+ } else {
3237
+ console.log(` ${pc8.yellow("!")} ${modifiedFiles.length} file(s) manually modified:`);
3238
+ for (const file of modifiedFiles) {
3239
+ const label = file.type === "agents" ? "(global block)" : "";
3240
+ console.log(` ${pc8.yellow("~")} ${file.path} ${pc8.dim(label)}`);
3241
+ }
3242
+ console.log();
3243
+ console.log(pc8.dim(" These files will be overwritten on next sync. To preserve changes,"));
3244
+ console.log(pc8.dim(" copy them elsewhere before running `agent-conf sync`."));
3245
+ }
3246
+ console.log();
3247
+ }
3248
+ const lockfilePath = formatPath(path14.join(targetDir, ".agent-conf", "agent-conf.lock"));
3249
+ console.log(pc8.dim(`Lock file: ${lockfilePath}`));
3250
+ console.log(pc8.dim(`CLI version: ${lockfile.cli_version}`));
3251
+ console.log();
3252
+ }
3253
+
3254
+ // src/commands/sync.ts
3255
+ import * as prompts6 from "@clack/prompts";
3256
+ import pc9 from "picocolors";
3257
+ async function syncCommand(options) {
3258
+ const logger = createLogger();
3259
+ console.log();
3260
+ prompts6.intro(pc9.bold("agent-conf sync"));
3261
+ if (options.pinned && options.ref) {
3262
+ logger.error("Cannot use --pinned with --ref. Choose one.");
3263
+ process.exit(1);
3264
+ }
3265
+ if (options.pinned && options.local !== void 0) {
3266
+ logger.error("Cannot use --pinned with --local.");
3267
+ process.exit(1);
3268
+ }
3269
+ const targetDir = await resolveTargetDirectory();
3270
+ const targets = await parseAndValidateTargets(options.target);
3271
+ const status = await getSyncStatus(targetDir);
3272
+ if (status.schemaError) {
3273
+ logger.error(status.schemaError);
3274
+ process.exit(1);
3275
+ }
3276
+ if (status.schemaWarning) {
3277
+ logger.warn(status.schemaWarning);
3278
+ }
3279
+ if (!status.hasSynced) {
3280
+ logger.warn(
3281
+ "This repository has not been synced yet. Consider running 'agent-conf init' first."
3282
+ );
3283
+ }
3284
+ let sourceRepo = options.source;
3285
+ if (!sourceRepo && status.lockfile?.source.type === "github") {
3286
+ sourceRepo = status.lockfile.source.repository;
3287
+ }
3288
+ const resolvedVersion = await resolveVersion(options, status, "sync", sourceRepo);
3289
+ if (!options.pinned && !options.ref && !options.local && status.lockfile?.pinned_version) {
3290
+ const currentVersion = status.lockfile.pinned_version;
3291
+ if (resolvedVersion.version) {
3292
+ const comparison = compareVersions(currentVersion, resolvedVersion.version);
3293
+ console.log();
3294
+ console.log(`Canonical source: ${pc9.cyan(sourceRepo)}`);
3295
+ console.log(`Latest release: ${pc9.cyan(resolvedVersion.version)}`);
3296
+ console.log(`Pinned version: ${pc9.cyan(currentVersion)}`);
3297
+ if (comparison >= 0) {
3298
+ console.log(` ${pc9.green("\u2713")} Up to date`);
3299
+ console.log();
3300
+ prompts6.outro(pc9.green("Already up to date!"));
3301
+ return;
3302
+ }
3303
+ console.log(
3304
+ ` ${pc9.yellow("\u2192")} Update available: ${currentVersion} \u2192 ${resolvedVersion.version}`
3305
+ );
3306
+ console.log();
3307
+ if (!options.yes) {
3308
+ const shouldUpdate = await prompts6.confirm({
3309
+ message: "Proceed with update?",
3310
+ initialValue: true
3311
+ });
3312
+ if (prompts6.isCancel(shouldUpdate) || !shouldUpdate) {
3313
+ prompts6.cancel("Sync cancelled");
3314
+ process.exit(0);
3315
+ }
3316
+ }
3317
+ }
3318
+ }
3319
+ if (options.ref && status.lockfile?.pinned_version) {
3320
+ const currentVersion = status.lockfile.pinned_version;
3321
+ if (resolvedVersion.version && currentVersion !== resolvedVersion.version) {
3322
+ logger.info(`Updating version: ${currentVersion} \u2192 ${resolvedVersion.version}`);
3323
+ }
3324
+ }
3325
+ const optionsWithSource = sourceRepo ? { ...options, source: sourceRepo } : options;
3326
+ const { resolvedSource, tempDir, repository } = await resolveSource(
3327
+ optionsWithSource,
3328
+ resolvedVersion
3329
+ );
3330
+ const shouldOverride = await promptMergeOrOverride(status, options, tempDir);
3331
+ await checkModifiedFilesBeforeSync(targetDir, targets, options, tempDir);
3332
+ await performSync({
3333
+ targetDir,
3334
+ resolvedSource,
3335
+ resolvedVersion,
3336
+ shouldOverride,
3337
+ targets,
3338
+ context: {
3339
+ commandName: "sync",
3340
+ status
3341
+ },
3342
+ tempDir,
3343
+ yes: options.yes,
3344
+ sourceRepo: repository,
3345
+ summaryFile: options.summaryFile,
3346
+ expandChanges: options.expandChanges
3347
+ });
3348
+ }
3349
+
3350
+ // src/commands/upgrade-cli.ts
3351
+ import { execSync as execSync2 } from "child_process";
3352
+ import * as prompts7 from "@clack/prompts";
3353
+ import pc10 from "picocolors";
3354
+ var NPM_PACKAGE_NAME = "agent-conf";
3355
+ async function getLatestNpmVersion() {
3356
+ const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`);
3357
+ if (!response.ok) {
3358
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
3359
+ }
3360
+ const data = await response.json();
3361
+ return data.version;
3362
+ }
3363
+ async function upgradeCliCommand(options) {
3364
+ const logger = createLogger();
3365
+ const currentVersion = getCliVersion();
3366
+ console.log();
3367
+ prompts7.intro(pc10.bold("agent-conf upgrade-cli"));
3368
+ const spinner = logger.spinner("Checking for CLI updates...");
3369
+ spinner.start();
3370
+ let latestVersion;
3371
+ try {
3372
+ latestVersion = await getLatestNpmVersion();
3373
+ spinner.stop();
3374
+ } catch (error) {
3375
+ spinner.fail("Failed to check for CLI updates");
3376
+ logger.error(error instanceof Error ? error.message : String(error));
3377
+ process.exit(1);
3378
+ }
3379
+ console.log();
3380
+ console.log(`Current version: ${pc10.cyan(currentVersion)}`);
3381
+ console.log(`Latest version: ${pc10.cyan(latestVersion)}`);
3382
+ const needsUpdate = compareVersions(currentVersion, latestVersion) < 0;
3383
+ if (!needsUpdate) {
3384
+ console.log();
3385
+ prompts7.outro(pc10.green("CLI is already up to date!"));
3386
+ return;
3387
+ }
3388
+ console.log();
3389
+ console.log(`${pc10.yellow("\u2192")} Update available: ${currentVersion} \u2192 ${latestVersion}`);
3390
+ console.log();
3391
+ if (!options.yes) {
3392
+ const shouldUpdate = await prompts7.confirm({
3393
+ message: "Proceed with CLI upgrade?",
3394
+ initialValue: true
3395
+ });
3396
+ if (prompts7.isCancel(shouldUpdate) || !shouldUpdate) {
3397
+ prompts7.cancel("Upgrade cancelled");
3398
+ process.exit(0);
3399
+ }
3400
+ }
3401
+ const installSpinner = logger.spinner("Upgrading CLI...");
3402
+ installSpinner.start();
3403
+ try {
3404
+ execSync2(`npm install -g ${NPM_PACKAGE_NAME}@latest`, {
3405
+ stdio: "pipe"
3406
+ });
3407
+ installSpinner.succeed("CLI upgraded");
3408
+ console.log();
3409
+ prompts7.outro(pc10.green(`CLI upgraded to ${latestVersion}!`));
3410
+ } catch (error) {
3411
+ installSpinner.fail("Upgrade failed");
3412
+ logger.error(error instanceof Error ? error.message : String(error));
3413
+ logger.info(`
3414
+ You can try manually: npm install -g ${NPM_PACKAGE_NAME}@latest`);
3415
+ process.exit(1);
3416
+ }
3417
+ }
3418
+
3419
+ // src/cli.ts
3420
+ if (handleCompletion()) {
3421
+ process.exit(0);
3422
+ }
3423
+ async function warnIfCliOutdated() {
3424
+ try {
3425
+ const cwd = process.cwd();
3426
+ const gitRoot = await getGitRoot(cwd);
3427
+ if (!gitRoot) return;
3428
+ const mismatch = await checkCliVersionMismatch(gitRoot);
3429
+ if (mismatch) {
3430
+ console.log();
3431
+ console.log(
3432
+ pc11.yellow(
3433
+ `\u26A0 CLI is outdated: v${mismatch.currentVersion} installed, but repo was synced with v${mismatch.lockfileVersion}`
3434
+ )
3435
+ );
3436
+ console.log(pc11.yellow(" Run: agent-conf upgrade-cli"));
3437
+ console.log();
3438
+ }
3439
+ } catch {
3440
+ }
3441
+ }
3442
+ function createCli() {
3443
+ const program = new Command();
3444
+ program.name("agent-conf").description("Sync company engineering standards from agent-conf repository").version(getCliVersion()).hook("preAction", async () => {
3445
+ await warnIfCliOutdated();
3446
+ });
3447
+ program.command("init").description("Initialize or sync agent-conf standards to the current repository").option(
3448
+ "-s, --source <repo>",
3449
+ "Canonical repository in owner/repo format (e.g., acme/standards)"
3450
+ ).option("--local [path]", "Use local canonical repository (auto-discover or specify path)").option("-y, --yes", "Non-interactive mode (merge by default)").option("--override", "Override existing AGENTS.md instead of merging").option("--ref <ref>", "GitHub ref/version to sync from (default: latest release)").option("-t, --target <targets...>", "Target platforms (claude, codex)", ["claude"]).action(
3451
+ async (options) => {
3452
+ await initCommand(options);
3453
+ }
3454
+ );
3455
+ program.command("sync").description("Sync content from canonical repository (fetches latest by default)").option(
3456
+ "-s, --source <repo>",
3457
+ "Canonical repository in owner/repo format (e.g., acme/standards)"
3458
+ ).option("--local [path]", "Use local canonical repository (auto-discover or specify path)").option("-y, --yes", "Non-interactive mode (merge by default)").option("--override", "Override existing AGENTS.md instead of merging").option("--ref <ref>", "GitHub ref/version to sync from").option("--pinned", "Use lockfile version without fetching latest").option("-t, --target <targets...>", "Target platforms (claude, codex)", ["claude"]).option("--summary-file <path>", "Write sync summary to file (markdown, for CI)").option("--expand-changes", "Show all items in output (default: first 5)").action(
3459
+ async (options) => {
3460
+ await syncCommand(options);
3461
+ }
3462
+ );
3463
+ program.command("status").description("Show current sync status").option("-c, --check", "Check for manually modified skill files").action(async (options) => {
3464
+ await statusCommand(options);
3465
+ });
3466
+ program.command("check").description("Check if managed files have been modified").option("-q, --quiet", "Minimal output, just exit code").action(async (options) => {
3467
+ await checkCommand(options);
3468
+ });
3469
+ program.command("upgrade-cli").description("Upgrade the agent-conf CLI to the latest version").option("-y, --yes", "Non-interactive mode").action(async (options) => {
3470
+ await upgradeCliCommand(options);
3471
+ });
3472
+ const configCmd = program.command("config").description("Manage global CLI configuration");
3473
+ configCmd.command("show").description("Show all configuration values").action(async () => {
3474
+ await configShowCommand();
3475
+ });
3476
+ configCmd.command("get <key>").description("Get a configuration value").action(async (key) => {
3477
+ await configGetCommand(key);
3478
+ });
3479
+ configCmd.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
3480
+ await configSetCommand(key, value);
3481
+ });
3482
+ configCmd.action(async () => {
3483
+ await configShowCommand();
3484
+ });
3485
+ const completionCmd = program.command("completion").description("Manage shell completions (bash, zsh, fish)");
3486
+ completionCmd.command("install").description("Install shell completions for your current shell").action(async () => {
3487
+ await installCompletion();
3488
+ });
3489
+ completionCmd.command("uninstall").description("Remove shell completions").action(async () => {
3490
+ await uninstallCompletion();
3491
+ });
3492
+ completionCmd.action(async () => {
3493
+ await installCompletion();
3494
+ });
3495
+ const canonicalCmd = program.command("canonical").description("Manage canonical repositories");
3496
+ canonicalCmd.command("init").description("Scaffold a new canonical repository structure").option("-n, --name <name>", "Name for the canonical repository").option("-o, --org <organization>", "Organization name").option("-d, --dir <directory>", "Target directory (default: current)").option("--marker-prefix <prefix>", "Marker prefix (default: agent-conf)").option("--no-examples", "Skip example skill creation").option("--rules-dir <directory>", "Rules directory (e.g., 'rules')").option("-y, --yes", "Non-interactive mode").action(
3497
+ async (options) => {
3498
+ await canonicalInitCommand({
3499
+ name: options.name,
3500
+ org: options.org,
3501
+ dir: options.dir,
3502
+ markerPrefix: options.markerPrefix,
3503
+ includeExamples: options.examples,
3504
+ rulesDir: options.rulesDir,
3505
+ yes: options.yes
3506
+ });
3507
+ }
3508
+ );
3509
+ canonicalCmd.action(() => {
3510
+ canonicalCmd.help();
3511
+ });
3512
+ program.action(() => {
3513
+ program.help();
3514
+ });
3515
+ return program;
3516
+ }
3517
+
3518
+ // src/index.ts
3519
+ var cli = createCli();
3520
+ cli.parse(process.argv);
3521
+ //# sourceMappingURL=index.js.map