@gitset-dev/cli 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,6 +122,14 @@ org policy locking the setting), the scheduled update still runs and
122
122
  commits safely; only the review-PR step fails, and the workflow run shows
123
123
  that failure clearly rather than hide it.
124
124
 
125
+ Some organizations enforce that setting off with no repo-level override at
126
+ all — the checkbox greys out even on the org's own settings page. For that
127
+ case, add a repository secret named `GITSET_PR_TOKEN` with a personal
128
+ access token (classic: `repo` scope; fine-grained: Pull requests write).
129
+ It's not subject to that organization-wide Actions restriction, and the
130
+ generated workflow picks it up automatically — no need to re-run `automate`
131
+ or touch the workflow file.
132
+
125
133
  ## Templates
126
134
 
127
135
  Define your commit style, issue structure, PR format, README skeleton, or
package/index.js CHANGED
@@ -132,9 +132,11 @@ before anything reaches your AI provider.
132
132
  generate full run (always shows the estimate and asks first)
133
133
  update incremental: re-summarizes only changed modules
134
134
  automate write a GitHub Actions workflow that runs update in CI
135
- and opens a PR when docs change (always asks first)
135
+ (commits directly by default; --sync pr for review PRs,
136
+ with the required access set up for you — always asks first)
136
137
  --since <ref> update selector via git diff instead of content hashes
137
138
  --mode <m> automate trigger: push | releases | weekly
139
+ --sync <s> automate apply style: commit (default) | pr
138
140
  --provider <p> override the default provider for this run
139
141
  --model <m> override the model
140
142
  --yes skip confirmations (CI)`,
@@ -8,6 +8,11 @@ const MODES = {
8
8
  weekly: 'weekly (Mondays 06:00 UTC)',
9
9
  };
10
10
 
11
+ const SYNC_MODES = {
12
+ commit: 'commit updates directly to the default branch (zero setup, works everywhere)',
13
+ pr: 'open a review pull request for each update',
14
+ };
15
+
11
16
  function buildTrigger(mode, defaultBranch) {
12
17
  if (mode === 'push') {
13
18
  return [
@@ -41,9 +46,97 @@ function buildTrigger(mode, defaultBranch) {
41
46
  throw new Error(`Unknown automation mode "${mode}". One of: ${Object.keys(MODES).join(', ')}`);
42
47
  }
43
48
 
44
- function buildKnowledgeWorkflow({ mode, provider, envKey, model, defaultBranch = 'main' }) {
49
+ function commitModeLines(defaultBranch) {
50
+ return {
51
+ header: [
52
+ '#',
53
+ `# Updates are committed directly to ${defaultBranch} using the built-in`,
54
+ '# Actions token — no extra permissions, tokens, or settings needed.',
55
+ "# It cannot re-trigger itself: GitHub never runs workflows for pushes",
56
+ '# made with the built-in token, and the path filters ignore these',
57
+ '# files anyway.',
58
+ ],
59
+ permissions: [
60
+ 'permissions:',
61
+ ' contents: write',
62
+ ],
63
+ checkoutWith: [
64
+ ' with:',
65
+ ` ref: ${defaultBranch}`,
66
+ ],
67
+ finalStep: [
68
+ ' - name: Commit the updated knowledge base',
69
+ ' run: |',
70
+ ' if git diff --quiet -- docs/gitset-knowledge AGENTS.md; then',
71
+ ' echo "Knowledge base already up to date."',
72
+ ' exit 0',
73
+ ' fi',
74
+ ' git config user.name "github-actions[bot]"',
75
+ ' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
76
+ ' git add docs/gitset-knowledge AGENTS.md',
77
+ ' git commit -m "docs: refresh knowledge base"',
78
+ ` git push origin ${defaultBranch} || (git pull --rebase origin ${defaultBranch} && git push origin ${defaultBranch})`,
79
+ ` echo "Knowledge base refreshed directly on ${defaultBranch}."`,
80
+ ],
81
+ };
82
+ }
83
+
84
+ function prModeLines() {
85
+ return {
86
+ header: [
87
+ '#',
88
+ '# PR mode needs ONE of the following (gitset knowledge automate sets',
89
+ '# this up for you):',
90
+ '# a. "Allow GitHub Actions to create and approve pull requests"',
91
+ '# enabled under Settings > Actions > General, OR',
92
+ '# b. a repository secret GITSET_PR_TOKEN holding a GitHub token',
93
+ '# that can open pull requests (used automatically when present;',
94
+ '# not subject to organization-level Actions restrictions).',
95
+ '# Without either, the update still commits and pushes its branch',
96
+ '# safely — only opening the review PR fails, and this job reports',
97
+ '# that as a failure so it is never silently missed.',
98
+ ],
99
+ permissions: [
100
+ 'permissions:',
101
+ ' contents: write',
102
+ ' pull-requests: write',
103
+ ],
104
+ checkoutWith: [],
105
+ finalStep: [
106
+ ' - name: Open a PR when the knowledge base changed',
107
+ ' env:',
108
+ ' GH_TOKEN: ${{ secrets.GITSET_PR_TOKEN || github.token }}',
109
+ ' run: |',
110
+ ' if git diff --quiet -- docs/gitset-knowledge AGENTS.md; then',
111
+ ' echo "Knowledge base already up to date."',
112
+ ' exit 0',
113
+ ' fi',
114
+ ' git config user.name "github-actions[bot]"',
115
+ ' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
116
+ ' git switch -c gitset/knowledge-update',
117
+ ' git add docs/gitset-knowledge AGENTS.md',
118
+ ' git commit -m "docs: refresh knowledge base"',
119
+ ' git push -f origin gitset/knowledge-update',
120
+ ' echo "Committed and pushed gitset/knowledge-update — this work is now safe on GitHub regardless of the next step."',
121
+ ' if PR_OUTPUT=$(gh pr create --title "docs: refresh knowledge base" --body "Automated incremental update by [Gitset](https://gitset.dev) Knowledge Mapper. Only changed modules were re-analyzed; review like any other docs change." 2>&1); then',
122
+ ' echo "$PR_OUTPUT"',
123
+ ' elif echo "$PR_OUTPUT" | grep -qi "already exists"; then',
124
+ ' echo "A pull request for gitset/knowledge-update already exists — nothing further to do."',
125
+ ' echo "$PR_OUTPUT"',
126
+ ' else',
127
+ ' echo "$PR_OUTPUT"',
128
+ ' echo "::error::Could not open the pull request automatically (the branch and commit ARE safely pushed — no work was lost). Run \'gitset knowledge automate\' again and pick PR mode to set this up automatically, or add a repository secret GITSET_PR_TOKEN with a GitHub token that can open PRs, or open it yourself: https://github.com/${{ github.repository }}/pull/new/gitset/knowledge-update"',
129
+ ' exit 1',
130
+ ' fi',
131
+ ],
132
+ };
133
+ }
134
+
135
+ function buildKnowledgeWorkflow({ mode, syncMode = 'commit', provider, envKey, model, defaultBranch = 'main' }) {
45
136
  if (!provider || !envKey) throw new Error('provider and envKey are required');
137
+ if (!SYNC_MODES[syncMode]) throw new Error(`Unknown sync mode "${syncMode}". One of: ${Object.keys(SYNC_MODES).join(', ')}`);
46
138
  const updateCmd = `gitset knowledge update --yes --provider ${provider}${model ? ` --model ${model}` : ''}`;
139
+ const sync = syncMode === 'commit' ? commitModeLines(defaultBranch) : prModeLines();
47
140
 
48
141
  return [
49
142
  'name: Gitset Knowledge Mapper',
@@ -53,26 +146,9 @@ function buildKnowledgeWorkflow({ mode, provider, envKey, model, defaultBranch =
53
146
  '# provider — never to Gitset. Runs whose mapped source files are',
54
147
  '# unchanged exit without any AI call (content-hash diff), so harmless',
55
148
  '# triggers cost nothing beyond a few CI seconds.',
56
- '#',
57
- '# Also requires "Allow GitHub Actions to create and approve pull',
58
- '# requests" to be enabled: Settings > Actions > General > Workflow',
59
- '# permissions. Without it, the update still commits and pushes',
60
- '# safely, but opening the review PR fails and this job reports it',
61
- '# as a failure so it is never silently missed.',
62
- '#',
63
- '# If your organization enforces that setting off and it cannot be',
64
- '# changed (the checkbox is greyed out even at the org level), add an',
65
- '# optional repository secret GITSET_PR_TOKEN — a personal access',
66
- '# token (classic: repo scope; fine-grained: Pull requests write) —',
67
- '# and this workflow uses it instead of the default token, which is',
68
- "# the standard workaround: an organization's PR-creation restriction",
69
- '# applies to the auto-generated Actions token, not to a real PAT.',
70
- '# No workflow changes needed after adding it — the next run picks it',
71
- '# up automatically.',
149
+ ...sync.header,
72
150
  ...buildTrigger(mode, defaultBranch),
73
- 'permissions:',
74
- ' contents: write',
75
- ' pull-requests: write',
151
+ ...sync.permissions,
76
152
  'concurrency:',
77
153
  ' group: gitset-knowledge',
78
154
  ' cancel-in-progress: false',
@@ -81,6 +157,7 @@ function buildKnowledgeWorkflow({ mode, provider, envKey, model, defaultBranch =
81
157
  ' runs-on: ubuntu-latest',
82
158
  ' steps:',
83
159
  ' - uses: actions/checkout@v4',
160
+ ...sync.checkoutWith,
84
161
  ' - uses: actions/setup-node@v4',
85
162
  ' with:',
86
163
  " node-version: '20'",
@@ -89,31 +166,7 @@ function buildKnowledgeWorkflow({ mode, provider, envKey, model, defaultBranch =
89
166
  ' env:',
90
167
  ` ${envKey}: \${{ secrets.${envKey} }}`,
91
168
  ` run: ${updateCmd}`,
92
- ' - name: Open a PR when the knowledge base changed',
93
- ' env:',
94
- ' GH_TOKEN: ${{ secrets.GITSET_PR_TOKEN || github.token }}',
95
- ' run: |',
96
- ' if git diff --quiet -- docs/gitset-knowledge AGENTS.md; then',
97
- ' echo "Knowledge base already up to date."',
98
- ' exit 0',
99
- ' fi',
100
- ' git config user.name "github-actions[bot]"',
101
- ' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
102
- ' git switch -c gitset/knowledge-update',
103
- ' git add docs/gitset-knowledge AGENTS.md',
104
- ' git commit -m "docs: refresh knowledge base"',
105
- ' git push -f origin gitset/knowledge-update',
106
- ' echo "Committed and pushed gitset/knowledge-update — this work is now safe on GitHub regardless of the next step."',
107
- ' if PR_OUTPUT=$(gh pr create --title "docs: refresh knowledge base" --body "Automated incremental update by [Gitset](https://gitset.dev) Knowledge Mapper. Only changed modules were re-analyzed; review like any other docs change." 2>&1); then',
108
- ' echo "$PR_OUTPUT"',
109
- ' elif echo "$PR_OUTPUT" | grep -qi "already exists"; then',
110
- ' echo "A pull request for gitset/knowledge-update already exists — nothing further to do."',
111
- ' echo "$PR_OUTPUT"',
112
- ' else',
113
- ' echo "$PR_OUTPUT"',
114
- ' echo "::error::Could not open the pull request automatically (the branch and commit ARE safely pushed — no work was lost). Try, in order: (1) enable \'Allow GitHub Actions to create and approve pull requests\' under Settings > Actions > General > Workflow permissions; (2) if that checkbox is greyed out, your organization enforces it off — add a repository secret named GITSET_PR_TOKEN with a personal access token (repo scope) and re-run, no workflow change needed; (3) open the PR yourself: https://github.com/${{ github.repository }}/pull/new/gitset/knowledge-update"',
115
- ' exit 1',
116
- ' fi',
169
+ ...sync.finalStep,
117
170
  '',
118
171
  ].join('\n');
119
172
  }
@@ -121,5 +174,6 @@ function buildKnowledgeWorkflow({ mode, provider, envKey, model, defaultBranch =
121
174
  module.exports = {
122
175
  WORKFLOW_PATH,
123
176
  MODES,
177
+ SYNC_MODES,
124
178
  buildKnowledgeWorkflow,
125
179
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitset-dev/cli",
3
- "version": "2.4.3",
3
+ "version": "2.5.0",
4
4
  "description": "Gitset CLI — drafts commits, PRs, issues, READMEs and release notes with your own AI key. Fully local: your code and keys never touch a Gitset server. Draft. Refine. Ship.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -521,11 +521,65 @@ async function ensurePrPermission(repo, argv) {
521
521
  return 'enabled';
522
522
  } catch {
523
523
  log('Could not change it automatically — you may not have admin access on this repo, or an organization policy locks this setting (if it appears greyed out in the GitHub UI at both the repo and org level, that\'s a hard enforcement you cannot toggle either way).', 'yellow');
524
- log('Workaround: add a repository secret named GITSET_PR_TOKEN with a personal access token (repo scope). It is not subject to the organization\'s Actions PR-creation restriction, and the workflow picks it up automatically — no need to re-run automate.', 'dim');
525
524
  return 'failed';
526
525
  }
527
526
  }
528
527
 
528
+ async function provisionPrToken(repo, argv) {
529
+ if (!repo.includes('/')) return 'unavailable';
530
+
531
+ const existing = (() => {
532
+ try {
533
+ const out = execFileSync('gh', ['secret', 'list', '--repo', repo], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
534
+ return out.includes('GITSET_PR_TOKEN');
535
+ } catch {
536
+ return false;
537
+ }
538
+ })();
539
+ if (existing) {
540
+ log('The GITSET_PR_TOKEN secret already exists in this repository — PR mode is ready.', 'green');
541
+ return 'already-provisioned';
542
+ }
543
+
544
+ log('\nNo problem — this can be fixed right now, automatically:', 'cyan');
545
+ log(' Your repository (or its organization) blocks the built-in CI token from opening pull requests.', 'reset');
546
+ log(' The fix: store a token from YOUR current GitHub login (the same one the gh CLI already uses) as an encrypted secret named GITSET_PR_TOKEN in this repository.', 'reset');
547
+ log(' · You don\'t create anything manually — no settings pages, no token forms.', 'dim');
548
+ log(' · The token is stored encrypted by GitHub, in this repository only.', 'dim');
549
+ log(' · CI will open pull requests as you, which your organization does allow.', 'dim');
550
+ log(' · Undo anytime: delete the GITSET_PR_TOKEN secret and CI falls back to the built-in token.', 'dim');
551
+
552
+ const proceed = await confirmOrAbort('\nStore it now?', argv);
553
+ if (!proceed) return 'declined';
554
+
555
+ let token = '';
556
+ try {
557
+ token = execFileSync('gh', ['auth', 'token'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
558
+ } catch { }
559
+ if (!token) {
560
+ log('Could not read a token from your gh login. Run "gh auth login" first, then re-run: gitset knowledge automate', 'red');
561
+ return 'failed';
562
+ }
563
+
564
+ try {
565
+ execFileSync('gh', ['secret', 'set', 'GITSET_PR_TOKEN', '--repo', repo], { input: token, stdio: ['pipe', 'pipe', 'pipe'] });
566
+ } catch {
567
+ log('Could not store the secret (you may not have admin access on this repository).', 'red');
568
+ return 'failed';
569
+ }
570
+
571
+ try {
572
+ const out = execFileSync('gh', ['secret', 'list', '--repo', repo], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
573
+ if (!out.includes('GITSET_PR_TOKEN')) throw new Error('not visible');
574
+ } catch {
575
+ log('The secret was sent but could not be verified — check https://github.com/' + repo + '/settings/secrets/actions', 'yellow');
576
+ return 'unverified';
577
+ }
578
+
579
+ log('Done — GITSET_PR_TOKEN is stored. CI can now open pull requests, starting with the very next run.', 'green');
580
+ return 'provisioned';
581
+ }
582
+
529
583
  async function runAutomate(rootDir, argv) {
530
584
  let cfg;
531
585
  try {
@@ -566,8 +620,44 @@ async function runAutomate(rootDir, argv) {
566
620
  return 1;
567
621
  }
568
622
 
623
+ const repo = repoLabel(rootDir);
624
+
625
+ let syncMode = flag(argv, '--sync');
626
+ if (!syncMode) {
627
+ if (!process.stdin.isTTY || argv.includes('--yes') || argv.includes('-y')) {
628
+ syncMode = 'commit';
629
+ } else {
630
+ syncMode = await selectOption('How should CI apply each update?', [
631
+ { label: `Commit directly to ${defaultBranch} (recommended — zero setup, works on every repo and org)`, value: 'commit' },
632
+ { label: 'Open a review pull request for each update (one extra one-time step — automated for you)', value: 'pr' },
633
+ { label: 'Cancel', value: 'cancel' },
634
+ ]);
635
+ if (syncMode === 'cancel') {
636
+ log('Nothing written.', 'yellow');
637
+ return 0;
638
+ }
639
+ }
640
+ }
641
+ if (!km.SYNC_MODES[syncMode]) {
642
+ log(`Unknown sync mode "${syncMode}". One of: ${Object.keys(km.SYNC_MODES).join(', ')}`, 'red');
643
+ return 1;
644
+ }
645
+
646
+ let prReady = null;
647
+ if (syncMode === 'pr') {
648
+ const prPermission = await ensurePrPermission(repo, argv);
649
+ if (prPermission === 'enabled' || prPermission === 'already-enabled') {
650
+ prReady = 'permission';
651
+ } else {
652
+ const patStatus = await provisionPrToken(repo, argv);
653
+ if (patStatus === 'provisioned' || patStatus === 'already-provisioned') prReady = 'token';
654
+ else if (patStatus === 'unverified') prReady = 'token-unverified';
655
+ }
656
+ }
657
+
569
658
  const yaml = km.buildKnowledgeWorkflow({
570
659
  mode,
660
+ syncMode,
571
661
  provider: cfg.provider,
572
662
  envKey,
573
663
  model: cfg.model || null,
@@ -591,9 +681,6 @@ async function runAutomate(rootDir, argv) {
591
681
  fs.writeFileSync(target, yaml);
592
682
  log(`\nWrote ${km.WORKFLOW_PATH}.`, 'green');
593
683
 
594
- const repo = repoLabel(rootDir);
595
- const prPermission = await ensurePrPermission(repo, argv);
596
-
597
684
  log('\nNext steps:', 'cyan');
598
685
  log(` 1. Add the repository secret ${envKey} with your ${cfg.provider} API key:`, 'reset');
599
686
  if (repo.includes('/')) {
@@ -601,23 +688,24 @@ async function runAutomate(rootDir, argv) {
601
688
  } else {
602
689
  log(' GitHub repo > Settings > Secrets and variables > Actions', 'dim');
603
690
  }
604
- if (prPermission === 'enabled' || prPermission === 'already-enabled') {
605
- log(' 2. "Allow GitHub Actions to create and approve pull requests" is already on — the update PR step is ready.', 'reset');
691
+ log(' 2. Make sure the knowledge base itself is committed (gitset knowledge generate, then commit docs/gitset-knowledge/ and AGENTS.md).', 'reset');
692
+ log(' 3. Commit and push the workflow file.', 'reset');
693
+
694
+ if (syncMode === 'commit') {
695
+ log(`\nThat's it — CI will commit refreshed docs straight to ${defaultBranch} whenever mapped source changes. No permissions to touch, no tokens to create, and runs with no mapped changes make zero AI calls.`, 'dim');
696
+ log('Prefer reviewing each update as a pull request instead? Re-run: gitset knowledge automate --sync pr', 'dim');
606
697
  } else {
607
- log(' 2. Enable "Allow GitHub Actions to create and approve pull requests" (required for the update PR):', 'reset');
608
- if (repo.includes('/')) {
609
- log(` https://github.com/${repo}/settings/actions`, 'dim');
698
+ if (prReady === 'permission') {
699
+ log('\nPR mode is ready: GitHub Actions is allowed to open pull requests in this repository.', 'dim');
700
+ } else if (prReady === 'token') {
701
+ log('\nPR mode is ready: CI will open pull requests using the GITSET_PR_TOKEN secret that was just stored.', 'dim');
702
+ } else if (prReady === 'token-unverified') {
703
+ log(`\nPR mode should be ready, but the stored secret could not be verified — double-check https://github.com/${repo}/settings/secrets/actions`, 'yellow');
610
704
  } else {
611
- log(' GitHub repo > Settings > Actions > General > Workflow permissions', 'dim');
612
- }
613
- log(' Without this, the update still runs and commits safely — only opening the review PR fails, and the workflow run will show that failure clearly rather than hide it.', 'dim');
614
- if (prPermission === 'failed') {
615
- log(' If that checkbox is greyed out even at the organization level, it is enforced off and cannot be toggled: add a repository secret named GITSET_PR_TOKEN with a personal access token (repo scope) instead — the workflow uses it automatically, no changes needed.', 'dim');
705
+ log('\nPR mode is NOT ready yet: CI cannot open pull requests in this repository until one of the fixes above is applied. Updates will still commit and push their branch safely — only the PR step will fail, visibly. Re-run "gitset knowledge automate" anytime to finish the setup, or switch to zero-setup direct commits with: gitset knowledge automate --sync commit', 'yellow');
616
706
  }
707
+ log('When mapped source changes land, CI opens a PR on branch gitset/knowledge-update — you review it like any docs change. Runs with no mapped changes make zero AI calls.', 'dim');
617
708
  }
618
- log(' 3. Make sure the knowledge base itself is committed (gitset knowledge generate, then commit docs/gitset-knowledge/ and AGENTS.md).', 'reset');
619
- log(' 4. Commit and push the workflow file.', 'reset');
620
- log('\nWhen mapped source changes land, CI opens a PR on branch gitset/knowledge-update — you review it like any docs change. Runs with no mapped changes make zero AI calls.', 'dim');
621
709
  return 0;
622
710
  }
623
711
 
@@ -648,7 +736,8 @@ async function runKnowledgeCommand(argv = []) {
648
736
  log(' generate build docs/gitset-knowledge/ from source code (asks before spending)', 'dim');
649
737
  log(' update incremental refresh: only changed modules are re-summarized', 'dim');
650
738
  log(' automate write a CI workflow that keeps it fresh (always asks first)', 'dim');
651
- log(' flags --provider <p> --model <m> --yes --since <ref> (update) --mode <push|releases|weekly> (automate)', 'dim');
739
+ log(' flags --provider <p> --model <m> --yes --since <ref> (update)', 'dim');
740
+ log(' --mode <push|releases|weekly> --sync <commit|pr> (automate)', 'dim');
652
741
  return verb ? 1 : 0;
653
742
  }
654
743
  }