projecta-rrr 1.24.3 → 1.24.5

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/AGENTS.md CHANGED
@@ -96,4 +96,4 @@ The RRR loop follows five steps:
96
96
  - Use `$rrr-help` for full skill catalogue
97
97
  - Trigger phrases are case-sensitive: `$rrr-plan-phase` not `$rrr-PlanPhase`
98
98
 
99
- <!-- generated: 2026-05-12T23:35:46.267Z | source: commands/rrr/*.md | count: 59 skills -->
99
+ <!-- generated: 2026-05-13T01:06:46.790Z | source: commands/rrr/*.md | count: 59 skills -->
package/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to RRR will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.24.5] - 2026-05-13
8
+
9
+ ### Added
10
+ - **Coordinator auth modes** — `$rrr-provision-coordinator` now writes an `auth` block with `mode: claude-code-subscription` (default) or `mode: anthropic-api-key`. Subscription mode uses `~/.claude/.credentials.json` injected via the Cloudflare Secret `CLAUDE_CREDENTIALS_JSON`, eliminating the need for `ANTHROPIC_API_KEY` on the coordinator.
11
+ - **Cloudflare AI Gateway option** — when `--auth-mode anthropic-api-key`, optional `--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]` flags wire `ANTHROPIC_BASE_URL` to a gateway URL and add `CF_AI_GATEWAY_TOKEN` to required secrets when authenticated.
12
+
13
+ ### Changed
14
+ - **Coordinator README** — generated README now includes the sandbox boot snippet that writes `CLAUDE_CREDENTIALS_JSON` to `~/.claude/.credentials.json`, and only documents AI Gateway when API-key mode is selected.
15
+ - **`$rrr-coordinator-status`** — checks for `~/.claude/.credentials.json` or `CLAUDE_CREDENTIALS_JSON` env when subscription mode is active; falls back to API key / AI Gateway env checks otherwise.
16
+
17
+ ## [1.24.4] - 2026-05-12
18
+
19
+ ### Fixed
20
+ - **Sprite browser UAT readiness** — live Sprite status now fails when `.planning/RUNTIME.json` requests `gsd-browser` but the Sprite runtime does not have `gsd-browser` on `PATH`.
21
+
7
22
  ## [1.24.3] - 2026-05-12
8
23
 
9
24
  ### Fixed
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: rrr:provision-coordinator
3
3
  description: Create the Cloudflare coordinator contract for RRR team mode integration review
4
- argument-hint: "[--provider cloudflare-sandbox] [--name <coordinator-name>]"
4
+ argument-hint: "[--auth-mode claude-code-subscription|anthropic-api-key] [--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]] [--provider cloudflare-sandbox] [--name <coordinator-name>]"
5
5
  allowed-tools:
6
6
  - Read
7
7
  - Write
@@ -32,8 +32,13 @@ Prepare the repo's coordinator contract so a reusable Cloudflare Claude Code/San
32
32
 
33
33
  </process>
34
34
 
35
+ <auth_modes>
36
+ - `claude-code-subscription` (default) — coordinator runs Claude Code authenticated by an Anthropic subscription. Store the contents of `~/.claude/.credentials.json` as the Cloudflare Secret `CLAUDE_CREDENTIALS_JSON`; sandbox boot writes it to disk so Claude Code resumes the session. No `ANTHROPIC_API_KEY` required.
37
+ - `anthropic-api-key` — coordinator uses `ANTHROPIC_API_KEY` (per-token billing). Pair with `--ai-gateway-account-id <id> --ai-gateway-id <id>` to route Anthropic traffic through Cloudflare AI Gateway for caching, rate limits, and observability. Add `--ai-gateway-authenticated` if the gateway requires `cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN`.
38
+ </auth_modes>
39
+
35
40
  <constraints>
36
41
  - Coordinator is report-first; never auto-merge to the base branch.
37
42
  - Developer work remains in team Sprites and team branches.
38
- - Do not store secrets in planning files.
43
+ - Do not store secrets in planning files — `CLAUDE_CREDENTIALS_JSON` and AI Gateway tokens belong in Cloudflare Secrets.
39
44
  </constraints>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projecta-rrr",
3
- "version": "1.24.3",
3
+ "version": "1.24.5",
4
4
  "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
5
5
  "bin": {
6
6
  "projecta-rrr": "bin/install.js",
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const os = require('os');
4
5
  const path = require('path');
5
6
  const { execFileSync } = require('child_process');
6
7
  const { resolvePlanningContext, requireTeamName } = require('./state-router');
@@ -465,8 +466,14 @@ function readSpriteStatus(projectRoot, options) {
465
466
  '-lc',
466
467
  'printf "branch="; git branch --show-current; printf "\\nhead="; git rev-parse --short HEAD; printf "\\nclaude="; claude --version 2>/dev/null || true; printf "\\ngsd="; gsd-browser --version 2>/dev/null || true'
467
468
  ]);
468
- status.ok = probe.ok;
469
+ const needsGsd = (plan.runtime.uat && plan.runtime.uat.browser === 'gsd-browser') ||
470
+ (((plan.runtime.tools || {}).developer_sprite || []).includes('gsd-browser'));
471
+ const hasGsd = /(^|\n)gsd=.+/.test(probe.output || '');
472
+ status.ok = probe.ok && (!needsGsd || hasGsd);
469
473
  status.details.push(probe.output || (probe.ok ? 'ok' : 'probe failed'));
474
+ if (probe.ok && needsGsd && !hasGsd) {
475
+ status.details.push('gsd-browser missing from Sprite runtime');
476
+ }
470
477
  } else {
471
478
  const list = execCapture('sprite', ['list', '--prefix', row.sprite]);
472
479
  status.ok = list.ok && list.output.includes(row.sprite);
@@ -508,6 +515,61 @@ function coordinatorConfigPath(projectRoot) {
508
515
  return path.join(projectRoot, '.planning', 'coordinator', 'COORDINATOR.json');
509
516
  }
510
517
 
518
+ function buildAiGatewayBlock(opts) {
519
+ const accountId = opts.aiGatewayAccountId || opts.cfAccountId || null;
520
+ const gatewayId = opts.aiGatewayId || null;
521
+ if (!accountId || !gatewayId) return null;
522
+ const authenticated = opts.aiGatewayAuthenticated === true || opts.aiGatewayAuthenticated === 'true';
523
+ const baseUrl = `https://gateway.ai.cloudflare.com/v1/${accountId}/${gatewayId}/anthropic`;
524
+ return {
525
+ provider: 'cloudflare-ai-gateway',
526
+ account_id: accountId,
527
+ gateway_id: gatewayId,
528
+ anthropic_base_url: baseUrl,
529
+ authenticated,
530
+ runtime_env: { ANTHROPIC_BASE_URL: baseUrl },
531
+ notes: [
532
+ 'Only used when auth_mode = anthropic-api-key.',
533
+ 'Set ANTHROPIC_BASE_URL to route Anthropic API calls through the gateway.',
534
+ authenticated
535
+ ? 'Authenticated gateway: send `cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN` header on every request.'
536
+ : 'Unauthenticated gateway: ANTHROPIC_API_KEY is the only credential; gateway URL is the access boundary.'
537
+ ]
538
+ };
539
+ }
540
+
541
+ function buildAuthBlock(opts) {
542
+ const mode = opts.authMode || 'claude-code-subscription';
543
+ if (mode === 'claude-code-subscription') {
544
+ return {
545
+ mode,
546
+ description: 'Coordinator uses Claude Code with an Anthropic subscription (Max/Pro). No ANTHROPIC_API_KEY required.',
547
+ credentials_path: '~/.claude/.credentials.json',
548
+ injection: [
549
+ 'Persist the credentials file once (after a one-time `claude login`).',
550
+ 'For a Cloudflare sandbox: write the stored credentials JSON to ~/.claude/.credentials.json on container start (from a CF Secret, KV, or D1 row).',
551
+ 'For a Sprite: run `claude login` once interactively, then `sprite checkpoint create` to capture the authenticated state.'
552
+ ],
553
+ required_secrets: ['GITHUB_TOKEN', 'CLAUDE_CREDENTIALS_JSON'],
554
+ notes: [
555
+ 'CLAUDE_CREDENTIALS_JSON holds the contents of ~/.claude/.credentials.json — store as a CF Secret, not in repo.',
556
+ 'Subscription billing applies; no per-token API cost.'
557
+ ]
558
+ };
559
+ }
560
+ if (mode === 'anthropic-api-key') {
561
+ return {
562
+ mode,
563
+ description: 'Coordinator authenticates Claude Code with ANTHROPIC_API_KEY (per-token billing).',
564
+ required_secrets: ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN'],
565
+ notes: [
566
+ 'Optional: pair with Cloudflare AI Gateway via --ai-gateway-account-id/--ai-gateway-id for caching, rate limits, and observability.'
567
+ ]
568
+ };
569
+ }
570
+ throw new Error(`unknown auth_mode: ${mode}`);
571
+ }
572
+
511
573
  function writeCoordinatorProvision(options) {
512
574
  const opts = options || {};
513
575
  const projectRoot = opts.projectRoot || process.cwd();
@@ -515,8 +577,13 @@ function writeCoordinatorProvision(options) {
515
577
  const runtime = readRuntimeManifest(projectRoot);
516
578
  const dir = path.join(projectRoot, '.planning', 'coordinator');
517
579
  ensureDir(dir);
580
+ const auth = buildAuthBlock(opts);
581
+ const aiGateway = auth.mode === 'anthropic-api-key' ? buildAiGatewayBlock(opts) : null;
582
+ const requiredSecrets = [...auth.required_secrets];
583
+ if (aiGateway && aiGateway.authenticated) requiredSecrets.push('CF_AI_GATEWAY_TOKEN');
584
+
518
585
  const config = {
519
- schema_version: 1,
586
+ schema_version: 2,
520
587
  provider: opts.provider || 'cloudflare-sandbox',
521
588
  name: opts.name || `rrr-coordinator-${repoNameFor(teamConfig).toLowerCase()}`,
522
589
  generated_at: new Date().toISOString(),
@@ -526,7 +593,9 @@ function writeCoordinatorProvision(options) {
526
593
  integration_branch: teamConfig.github.integration_branch,
527
594
  runtime_manifest: path.relative(projectRoot, runtimeManifestPath(projectRoot)),
528
595
  report_first: true,
529
- required_secrets: ['ANTHROPIC_API_KEY', 'GITHUB_TOKEN'],
596
+ auth,
597
+ ai_gateway: aiGateway,
598
+ required_secrets: requiredSecrets,
530
599
  responsibilities: [
531
600
  'clone repository into ephemeral sandbox',
532
601
  'merge team branches into integration branch',
@@ -536,6 +605,7 @@ function writeCoordinatorProvision(options) {
536
605
  ]
537
606
  };
538
607
  writeJson(coordinatorConfigPath(projectRoot), config);
608
+
539
609
  const readme = [
540
610
  `# ${config.name}`,
541
611
  '',
@@ -548,11 +618,67 @@ function writeCoordinatorProvision(options) {
548
618
  `Base branch: ${config.base_branch}`,
549
619
  `Integration branch: ${config.integration_branch}`,
550
620
  `Runtime manifest: ${config.runtime_manifest}`,
621
+ '',
622
+ '## Auth',
623
+ '',
624
+ `Mode: \`${auth.mode}\``,
625
+ '',
626
+ auth.description
627
+ ];
628
+
629
+ if (auth.mode === 'claude-code-subscription') {
630
+ readme.push(
631
+ '',
632
+ '### Credentials injection',
633
+ '',
634
+ ...auth.injection.map(line => `- ${line}`),
635
+ '',
636
+ 'Cloudflare sandbox boot snippet:',
637
+ '',
638
+ '```bash',
639
+ 'mkdir -p ~/.claude',
640
+ 'printf "%s" "$CLAUDE_CREDENTIALS_JSON" > ~/.claude/.credentials.json',
641
+ 'chmod 600 ~/.claude/.credentials.json',
642
+ 'claude --version # verifies authenticated',
643
+ '```'
644
+ );
645
+ }
646
+
647
+ readme.push(
551
648
  '',
552
649
  '## Required Secrets',
553
650
  '',
554
- '- `ANTHROPIC_API_KEY` for Claude Code.',
555
- '- `GITHUB_TOKEN` or GitHub App installation token for read/write repo access.',
651
+ ...requiredSecrets.map(s => `- \`${s}\``)
652
+ );
653
+
654
+ if (aiGateway) {
655
+ readme.push(
656
+ '',
657
+ '## Cloudflare AI Gateway (optional)',
658
+ '',
659
+ `Account: ${aiGateway.account_id}`,
660
+ `Gateway: ${aiGateway.gateway_id}`,
661
+ `Anthropic base URL: ${aiGateway.anthropic_base_url}`,
662
+ `Authenticated: ${aiGateway.authenticated ? 'yes' : 'no'}`,
663
+ '',
664
+ '```bash',
665
+ `export ANTHROPIC_BASE_URL="${aiGateway.anthropic_base_url}"`,
666
+ 'export ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY"',
667
+ aiGateway.authenticated
668
+ ? '# header: cf-aig-authorization: Bearer $CF_AI_GATEWAY_TOKEN'
669
+ : '# unauthenticated gateway — URL is the access boundary',
670
+ '```'
671
+ );
672
+ } else if (auth.mode === 'anthropic-api-key') {
673
+ readme.push(
674
+ '',
675
+ '## Cloudflare AI Gateway',
676
+ '',
677
+ 'Not configured. Re-run with `--ai-gateway-account-id <id> --ai-gateway-id <id>` to route Anthropic API traffic through Cloudflare AI Gateway.'
678
+ );
679
+ }
680
+
681
+ readme.push(
556
682
  '',
557
683
  '## Execution Contract',
558
684
  '',
@@ -560,8 +686,9 @@ function writeCoordinatorProvision(options) {
560
686
  '- GitHub remains the source of truth for branches, PRs, reviews, and checks.',
561
687
  '- Coordinator is report-first: it writes reports and recommendations only; humans approve final merges.',
562
688
  '- Developer work stays in Sprites/team branches.'
563
- ].join('\n');
564
- atomicWrite(path.join(dir, 'README.md'), `${readme}\n`);
689
+ );
690
+
691
+ atomicWrite(path.join(dir, 'README.md'), `${readme.join('\n')}\n`);
565
692
  return { config, runtime, dir };
566
693
  }
567
694
 
@@ -573,8 +700,36 @@ function readCoordinatorStatus(projectRoot) {
573
700
  checks.push({ name: 'runtime manifest', ok: fs.existsSync(runtimeManifestPath(root)), detail: path.relative(root, runtimeManifestPath(root)) });
574
701
  const wrangler = execCapture('npx', ['wrangler', '--version'], { cwd: root });
575
702
  checks.push({ name: 'wrangler available', ok: wrangler.ok, detail: wrangler.output || 'not available' });
576
- checks.push({ name: 'ANTHROPIC_API_KEY present', ok: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? 'present' : 'missing locally' });
577
703
  checks.push({ name: 'GITHUB_TOKEN present', ok: !!process.env.GITHUB_TOKEN, detail: process.env.GITHUB_TOKEN ? 'present' : 'missing locally' });
704
+
705
+ const authMode = config && config.auth ? config.auth.mode : (config && config.ai_gateway ? 'anthropic-api-key' : 'claude-code-subscription');
706
+ if (authMode === 'claude-code-subscription') {
707
+ const credsPath = path.join(os.homedir(), '.claude', '.credentials.json');
708
+ const localCreds = fs.existsSync(credsPath);
709
+ checks.push({
710
+ name: 'Claude Code subscription credentials',
711
+ ok: localCreds || !!process.env.CLAUDE_CREDENTIALS_JSON,
712
+ detail: localCreds ? credsPath : (process.env.CLAUDE_CREDENTIALS_JSON ? 'CLAUDE_CREDENTIALS_JSON env present' : 'missing locally (run `claude login` or set CLAUDE_CREDENTIALS_JSON)')
713
+ });
714
+ } else {
715
+ checks.push({ name: 'ANTHROPIC_API_KEY present', ok: !!process.env.ANTHROPIC_API_KEY, detail: process.env.ANTHROPIC_API_KEY ? 'present' : 'missing locally' });
716
+ if (config && config.ai_gateway) {
717
+ const expectedBase = config.ai_gateway.anthropic_base_url;
718
+ const baseUrl = process.env.ANTHROPIC_BASE_URL;
719
+ checks.push({
720
+ name: 'ANTHROPIC_BASE_URL routes to AI Gateway',
721
+ ok: baseUrl === expectedBase,
722
+ detail: baseUrl ? (baseUrl === expectedBase ? expectedBase : `mismatch: ${baseUrl}`) : `missing locally (expected ${expectedBase})`
723
+ });
724
+ if (config.ai_gateway.authenticated) {
725
+ checks.push({
726
+ name: 'CF_AI_GATEWAY_TOKEN present',
727
+ ok: !!process.env.CF_AI_GATEWAY_TOKEN,
728
+ detail: process.env.CF_AI_GATEWAY_TOKEN ? 'present' : 'missing locally (authenticated gateway requires it)'
729
+ });
730
+ }
731
+ }
732
+ }
578
733
  return { config, checks };
579
734
  }
580
735
 
@@ -73,7 +73,7 @@ Usage:
73
73
  node scripts/rrr-team-mode.js runtime-init
74
74
  node scripts/rrr-team-mode.js provision-sprites [--apply] [--teams team-runtime]
75
75
  node scripts/rrr-team-mode.js sprite-status [--live]
76
- node scripts/rrr-team-mode.js provision-coordinator
76
+ node scripts/rrr-team-mode.js provision-coordinator [--auth-mode claude-code-subscription|anthropic-api-key] [--ai-gateway-account-id <id> --ai-gateway-id <id> [--ai-gateway-authenticated]]
77
77
  node scripts/rrr-team-mode.js coordinator-status
78
78
 
79
79
  Notes:
@@ -224,10 +224,18 @@ function main() {
224
224
  const result = writeCoordinatorProvision({
225
225
  projectRoot,
226
226
  provider: args.provider,
227
- name: args.name
227
+ name: args.name,
228
+ authMode: args['auth-mode'] || args.authMode,
229
+ aiGatewayAccountId: args['ai-gateway-account-id'] || args['cf-account-id'],
230
+ aiGatewayId: args['ai-gateway-id'],
231
+ aiGatewayAuthenticated: args['ai-gateway-authenticated'] === true || args['ai-gateway-authenticated'] === 'true'
228
232
  });
229
233
  console.log(`Wrote ${path.relative(projectRoot, path.join(result.dir, 'COORDINATOR.json'))}`);
230
234
  console.log(`Wrote ${path.relative(projectRoot, path.join(result.dir, 'README.md'))}`);
235
+ console.log(`Auth mode: ${result.config.auth.mode}`);
236
+ if (result.config.ai_gateway) {
237
+ console.log(`AI Gateway: ${result.config.ai_gateway.anthropic_base_url}`);
238
+ }
231
239
  return;
232
240
  }
233
241