sinapse-ai 7.7.2 → 7.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.claude/hooks/enforce-git-push-authority.sh +34 -2
  2. package/.claude/rules/safe-collaboration.md +12 -1
  3. package/.codex/catalog.json +157 -0
  4. package/.codex/command-registry.json +441 -0
  5. package/.codex/scripts/generate-codex-greeting.js +101 -0
  6. package/.codex/scripts/resolve-codex-command.js +147 -0
  7. package/.codex/skills/sinapse-analyst/SKILL.md +5 -4
  8. package/.codex/skills/sinapse-architect/SKILL.md +5 -4
  9. package/.codex/skills/sinapse-data-engineer/SKILL.md +5 -4
  10. package/.codex/skills/sinapse-dev/SKILL.md +5 -4
  11. package/.codex/skills/sinapse-devops/SKILL.md +5 -4
  12. package/.codex/skills/sinapse-orqx/SKILL.md +10 -15
  13. package/.codex/skills/sinapse-pm/SKILL.md +5 -4
  14. package/.codex/skills/sinapse-po/SKILL.md +4 -3
  15. package/.codex/skills/sinapse-qa/SKILL.md +12 -11
  16. package/.codex/skills/sinapse-sm/SKILL.md +5 -4
  17. package/.codex/skills/sinapse-squad-creator/SKILL.md +5 -4
  18. package/.codex/skills/sinapse-ux-design-expert/SKILL.md +5 -4
  19. package/.codex/tasks/convene-sinapse-council.md +28 -0
  20. package/.codex/tasks/create-sinapse-strategic-brief.md +29 -0
  21. package/.codex/tasks/onboard-sinapse-codex.md +34 -0
  22. package/.codex/tasks/plan-sinapse-initiative.md +33 -0
  23. package/.codex/tasks/resolve-sinapse-conflict.md +28 -0
  24. package/.codex/tasks/route-sinapse-request.md +33 -0
  25. package/.codex/tasks/status-sinapse-capabilities.md +28 -0
  26. package/.sinapse-ai/core-config.yaml +1 -1
  27. package/.sinapse-ai/data/entity-registry.yaml +903 -805
  28. package/.sinapse-ai/data/registry-update-log.jsonl +10 -0
  29. package/.sinapse-ai/infrastructure/scripts/codex-parity/catalog.js +123 -0
  30. package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/index.js +60 -11
  31. package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/validate.js +44 -16
  32. package/.sinapse-ai/infrastructure/scripts/sync-codex-local-first.js +156 -0
  33. package/.sinapse-ai/infrastructure/scripts/validate-codex-command-registry.js +264 -0
  34. package/.sinapse-ai/infrastructure/scripts/validate-codex-integration.js +15 -6
  35. package/.sinapse-ai/infrastructure/scripts/validate-codex-sync.js +156 -0
  36. package/.sinapse-ai/infrastructure/scripts/validate-parity.js +3 -1
  37. package/.sinapse-ai/infrastructure/scripts/validate-paths.js +8 -10
  38. package/.sinapse-ai/infrastructure/templates/safe-collab/README.md +52 -17
  39. package/.sinapse-ai/infrastructure/templates/safe-collab/apply.sh +85 -0
  40. package/.sinapse-ai/infrastructure/templates/safe-collab/safe-collaboration-rule.md +11 -0
  41. package/.sinapse-ai/install-manifest.yaml +41 -21
  42. package/.sinapse-ai/project-config.yaml +1 -1
  43. package/bin/utils/collab-start.js +267 -0
  44. package/bin/utils/git-branch-guard.js +76 -0
  45. package/bin/utils/pre-push-safety.js +110 -0
  46. package/bin/utils/staged-secret-scan.js +108 -0
  47. package/docs/ORQX-PLAN.md +3 -2
  48. package/docs/codex-parity-program.md +670 -0
  49. package/docs/codex-total-parity-orchestration-plan.md +301 -0
  50. package/docs/codex-workflow-task-parity.md +87 -0
  51. package/docs/collaboration-autonomy-plan.md +243 -0
  52. package/docs/guides/framework-contributor-mode.md +310 -0
  53. package/docs/guides/parallel-collaboration-source-of-truth.md +481 -0
  54. package/package.json +11 -3
  55. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +2 -2
  56. package/scripts/ensure-manifest.js +9 -0
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # apply.sh — Apply safe-collab template to a target project
3
+ # Usage: bash apply.sh <target-dir> <owner-github> <collab-github> [user1-prefix] [user2-prefix]
4
+ #
5
+ # Example:
6
+ # bash apply.sh /path/to/my-project caioimori Matheus-soier caio soier
7
+
8
+ set -e
9
+
10
+ TARGET="${1:?Usage: bash apply.sh <target-dir> <owner-github> <collab-github> [user1] [user2]}"
11
+ OWNER="${2:?Missing owner GitHub username}"
12
+ COLLAB="${3:?Missing collaborator GitHub username}"
13
+ USER1="${4:-dev1}"
14
+ USER2="${5:-dev2}"
15
+
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+
18
+ echo "=== Safe Collaboration Setup ==="
19
+ echo "Target: $TARGET"
20
+ echo "Owner: @$OWNER"
21
+ echo "Collaborator: @$COLLAB"
22
+ echo "Branch prefixes: $USER1/, $USER2/"
23
+ echo ""
24
+
25
+ # Create directories
26
+ mkdir -p "$TARGET/.claude/rules"
27
+ mkdir -p "$TARGET/.github"
28
+ mkdir -p "$TARGET/docs/guides"
29
+
30
+ # Copy and customize rule file
31
+ sed -e "s/{{USER_1}}/$USER1/g" \
32
+ -e "s/{{USER_2}}/$USER2/g" \
33
+ -e "s/{{user1}}/$USER1/g" \
34
+ -e "s/{{user2}}/$USER2/g" \
35
+ "$SCRIPT_DIR/safe-collaboration-rule.md" > "$TARGET/.claude/rules/safe-collaboration.md"
36
+ echo "[OK] .claude/rules/safe-collaboration.md"
37
+
38
+ # Copy PR template
39
+ cp "$SCRIPT_DIR/pull_request_template.md" "$TARGET/.github/PULL_REQUEST_TEMPLATE.md"
40
+ echo "[OK] .github/PULL_REQUEST_TEMPLATE.md"
41
+
42
+ # Copy and customize CODEOWNERS
43
+ sed -e "s/{{PROJECT_NAME}}/$(basename "$TARGET")/g" \
44
+ -e "s/{{OWNER_GITHUB}}/$OWNER/g" \
45
+ -e "s/{{COLLAB_GITHUB}}/$COLLAB/g" \
46
+ "$SCRIPT_DIR/CODEOWNERS.template" > "$TARGET/.github/CODEOWNERS"
47
+ echo "[OK] .github/CODEOWNERS"
48
+
49
+ # Copy workflow guide
50
+ cp "$SCRIPT_DIR/parallel-workflow-guide.md" "$TARGET/docs/guides/parallel-workflow.md"
51
+ echo "[OK] docs/guides/parallel-workflow.md"
52
+
53
+ # Add runtime patterns to .gitignore if not already present
54
+ GITIGNORE="$TARGET/.gitignore"
55
+ if [ -f "$GITIGNORE" ]; then
56
+ if ! grep -q '.sinapse-ai/.cache/' "$GITIGNORE" 2>/dev/null; then
57
+ echo "" >> "$GITIGNORE"
58
+ echo "# SINAPSE runtime artifacts" >> "$GITIGNORE"
59
+ echo ".sinapse-ai/.cache/" >> "$GITIGNORE"
60
+ echo ".sinapse-ai/.tmp/" >> "$GITIGNORE"
61
+ echo "[OK] .gitignore updated"
62
+ else
63
+ echo "[OK] .gitignore already has runtime patterns"
64
+ fi
65
+ else
66
+ echo "[SKIP] No .gitignore found"
67
+ fi
68
+
69
+ echo ""
70
+ echo "=== Files applied. Now configure GitHub: ==="
71
+ echo ""
72
+ echo "1. Settings > Rules > Rulesets > New ruleset (target: main)"
73
+ echo " - Block direct pushes"
74
+ echo " - Require 1 PR approval"
75
+ echo " - Block force pushes"
76
+ echo " - Block branch deletion"
77
+ echo " - Dismiss stale reviews"
78
+ echo ""
79
+ echo "2. Settings > Collaborators"
80
+ echo " - Add @$COLLAB with Write permission"
81
+ echo ""
82
+ echo "3. Settings > General"
83
+ echo " - Check 'Automatically delete head branches'"
84
+ echo ""
85
+ echo "Done. Safe collaboration is ready."
@@ -33,6 +33,17 @@ Before ANY work begins, the agent MUST:
33
33
 
34
34
  Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test`
35
35
 
36
+ **User Detection (priority order):**
37
+ 1. `git config user.name` -> lookup in mapping table (case-insensitive)
38
+ 2. `$USERNAME` (Windows) or `$USER` (Unix) -> lookup
39
+ 3. Fallback: `dev/`
40
+
41
+ | git config / env contains | Branch prefix |
42
+ |----------------------------|---------------|
43
+ | {{user1}} (case-insensitive) | `{{user1}}/` |
44
+ | {{user2}} (case-insensitive) | `{{user2}}/` |
45
+ | (anything else) | `dev/` |
46
+
36
47
  ## Before Every Commit — Safety Checks
37
48
 
38
49
  1. `git status` — verify only expected files changed
@@ -7,10 +7,10 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 7.7.2
11
- generated_at: "2026-04-02T01:18:27.515Z"
10
+ version: 7.7.4
11
+ generated_at: "2026-04-03T00:06:05.314Z"
12
12
  generator: scripts/generate-install-manifest.js
13
- file_count: 1112
13
+ file_count: 1117
14
14
  files:
15
15
  - path: cli/commands/config/index.js
16
16
  hash: sha256:66f111eceef0f60fa0a8904add783b615d55b01d5fe36408623c3dd828e702f6
@@ -189,9 +189,9 @@ files:
189
189
  type: cli
190
190
  size: 5907
191
191
  - path: core-config.yaml
192
- hash: sha256:1dfc00cfc993bacb17f683aacdf898b856744a33a76183adab2d1c1896be8b31
192
+ hash: sha256:fc24a8286b20ec2558c90186e99124b96d26b287ff107b747c2b68b1102e0cc8
193
193
  type: config
194
- size: 10514
194
+ size: 10513
195
195
  - path: core/code-intel/code-intel-client.js
196
196
  hash: sha256:6c9a08a37775acf90397aa079a4ad2c5edcc47f2cfd592b26ae9f3d154d1deb8
197
197
  type: core
@@ -1237,9 +1237,9 @@ files:
1237
1237
  type: data
1238
1238
  size: 9586
1239
1239
  - path: data/entity-registry.yaml
1240
- hash: sha256:0a28211d3375c09475cbcbe0753a0add546e8243d0853a99e998fd9fe642dcac
1240
+ hash: sha256:265b44f0e9c36e85375f05ba88dbf19ed9033244a6eea338f4b8e3a4e453dd12
1241
1241
  type: data
1242
- size: 512100
1242
+ size: 514885
1243
1243
  - path: data/learned-patterns.yaml
1244
1244
  hash: sha256:24ac0b160615583a0ff783d3da8af80b7f94191575d6db2054ec8e10a3f945dc
1245
1245
  type: data
@@ -3052,14 +3052,18 @@ files:
3052
3052
  hash: sha256:47b3f9044ce9b92b8c486a75da82afc4cc6b49d5d97e722ef5a1938fbf49943a
3053
3053
  type: script
3054
3054
  size: 40744
3055
+ - path: infrastructure/scripts/codex-parity/catalog.js
3056
+ hash: sha256:cfef97860788a119c44cbb18b4249ad538814f99a03b2ca13e66dd74aa8b5242
3057
+ type: script
3058
+ size: 3527
3055
3059
  - path: infrastructure/scripts/codex-skills-sync/index.js
3056
- hash: sha256:cea7c65175ebf5f1f7d47d0dc536e5889e420e6c137a41acd522be8b69fe6327
3060
+ hash: sha256:9eb2637a4f1f6edeabf358734b14e4c709bed37560f2e40aebf1ea3ef4572c3c
3057
3061
  type: script
3058
- size: 5268
3062
+ size: 7494
3059
3063
  - path: infrastructure/scripts/codex-skills-sync/validate.js
3060
- hash: sha256:07c2d17438890bc5bd3f2cc208692ff65f6d1f394390066ff9869e03973c7e2b
3064
+ hash: sha256:1a1301d2e3f558bedf3a1ab2c5c9ee2868a4c253068311d5014987917f367443
3061
3065
  type: script
3062
- size: 4577
3066
+ size: 5626
3063
3067
  - path: infrastructure/scripts/collect-tool-usage.js
3064
3068
  hash: sha256:510b621078602273fb146eef57d115b08988ea43d03eb5d92192050c13e95efe
3065
3069
  type: script
@@ -3372,6 +3376,10 @@ files:
3372
3376
  hash: sha256:ceb0450fa12fa48f0255bb4565858eb1a97b28c30b98d36cb61d52d72e08b054
3373
3377
  type: script
3374
3378
  size: 22394
3379
+ - path: infrastructure/scripts/sync-codex-local-first.js
3380
+ hash: sha256:ad5245ead7243a39cd5116f44fcf1234ad54b016e365dfefa017764a76f4f3eb
3381
+ type: script
3382
+ size: 4089
3375
3383
  - path: infrastructure/scripts/template-engine.js
3376
3384
  hash: sha256:de1bc8ce51eeabf6f6ec558d35107fbc4843ac76e3361044ba9f47bb6af4f058
3377
3385
  type: script
@@ -3420,22 +3428,30 @@ files:
3420
3428
  hash: sha256:4447fbf8cffdf20b34fad71eb4caf5aec0726f2b8f27cd49cd1bd59d69cdc009
3421
3429
  type: script
3422
3430
  size: 2838
3431
+ - path: infrastructure/scripts/validate-codex-command-registry.js
3432
+ hash: sha256:a37a4c5fc8a4c3a7ce7ba240aeea16abc0a75871751f66eeb84991604ff066bf
3433
+ type: script
3434
+ size: 7266
3423
3435
  - path: infrastructure/scripts/validate-codex-integration.js
3424
- hash: sha256:01065d27f1c9e002b3985f1b5090cf123369fcf528012c2ec8c4a02df9b3645e
3436
+ hash: sha256:c553cc493ddbfecf41b865f8a1075df56b6500c3b2b315bae8b7e7bcec4db85a
3425
3437
  type: script
3426
- size: 4127
3438
+ size: 4742
3439
+ - path: infrastructure/scripts/validate-codex-sync.js
3440
+ hash: sha256:f7f2963f995df0dc1fc0a4662146318aa4dba2699c8dbfc4fa748b3201b1db56
3441
+ type: script
3442
+ size: 4427
3427
3443
  - path: infrastructure/scripts/validate-output-pattern.js
3428
3444
  hash: sha256:91111d656e8d7b38a20a1bda753e663b74318f75cdab2025c7e0b84c775fc83d
3429
3445
  type: script
3430
3446
  size: 6692
3431
3447
  - path: infrastructure/scripts/validate-parity.js
3432
- hash: sha256:5cbd1c4458fb3e485872635e2210b3d3406c22381185039ef112db8912887201
3448
+ hash: sha256:284c7715cc65b76579284024bbe5176b9230340d841dbfe79559c8f5844109cb
3433
3449
  type: script
3434
- size: 12121
3450
+ size: 12266
3435
3451
  - path: infrastructure/scripts/validate-paths.js
3436
- hash: sha256:f657c32be91ed63e0b82d5b134b143d03174622eba60fb4b556adc570b8c7f5a
3452
+ hash: sha256:942f6de9e621b7331f61dc915db5d24b987b67d1ae3377882330f7013dd214e9
3437
3453
  type: script
3438
- size: 3779
3454
+ size: 3671
3439
3455
  - path: infrastructure/scripts/validate-user-profile.js
3440
3456
  hash: sha256:0f78a5aebd85b4b0d2a5b0f2d81ccbbbe0f2d25ed1cfcafeb129734852217683
3441
3457
  type: script
@@ -3508,6 +3524,10 @@ files:
3508
3524
  hash: sha256:4c9b70cacde88097d53f9d4d6e2319f65cabf6a076047195cdcf949d9e949512
3509
3525
  type: template
3510
3526
  size: 5567
3527
+ - path: infrastructure/templates/safe-collab/apply.sh
3528
+ hash: sha256:1f9d39834ff301e3ee1adf1779fc3dd9e22bd12c9a603f8ff11d430c1328ed82
3529
+ type: template
3530
+ size: 2738
3511
3531
  - path: infrastructure/templates/safe-collab/CODEOWNERS.template
3512
3532
  hash: sha256:988d5f6e373df03913d84c11cc42af42917f702920059ef296b2b236be7387e8
3513
3533
  type: template
@@ -3521,13 +3541,13 @@ files:
3521
3541
  type: template
3522
3542
  size: 338
3523
3543
  - path: infrastructure/templates/safe-collab/README.md
3524
- hash: sha256:7738672d855290ba8f039bf1b136650db75a948e57fc307136837cd121c4df42
3544
+ hash: sha256:0b4f3d0ce9b7111d812917312ae504df976245d902eecfa501f73e7aa0719813
3525
3545
  type: template
3526
- size: 1024
3546
+ size: 2179
3527
3547
  - path: infrastructure/templates/safe-collab/safe-collaboration-rule.md
3528
- hash: sha256:e68e7df44bcb54d1f9ae1d3b121027819b8a0a2ba7e6a20e0fd181c86301af5e
3548
+ hash: sha256:9631681340fddc314080f9182b76ccfbb1ee3441d92ce181067e76cace960a5e
3529
3549
  type: template
3530
- size: 2449
3550
+ size: 2871
3531
3551
  - path: infrastructure/templates/sinapse-sync.yaml.template
3532
3552
  hash: sha256:36ada7873fcbd5bab225b528c7be39035a12fc156135cc82c73d75782d7c53a2
3533
3553
  type: template
@@ -70,7 +70,7 @@ github_integration:
70
70
  "style/": "style"
71
71
  "build/": "build"
72
72
  default_type: "feat"
73
- auto_assign_reviewers: false
73
+ auto_assign_reviewers: true
74
74
  draft_by_default: false
75
75
  semantic_release:
76
76
  enabled: true
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync, execSync } = require('child_process');
4
+ const path = require('path');
5
+
6
+ function execGit(command) {
7
+ return execSync(command, {
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'pipe'],
10
+ }).trim();
11
+ }
12
+
13
+ function safeExec(command) {
14
+ try {
15
+ return execGit(command);
16
+ } catch {
17
+ return '';
18
+ }
19
+ }
20
+
21
+ function parseArgs(argv) {
22
+ const parsed = {
23
+ adoptCurrent: false,
24
+ check: false,
25
+ type: 'feat',
26
+ owner: null,
27
+ storyId: null,
28
+ slug: null,
29
+ };
30
+
31
+ for (const arg of argv) {
32
+ if (arg === '--adopt-current') {
33
+ parsed.adoptCurrent = true;
34
+ continue;
35
+ }
36
+ if (arg === '--check') {
37
+ parsed.check = true;
38
+ continue;
39
+ }
40
+ if (arg.startsWith('--type=')) {
41
+ parsed.type = arg.split('=')[1] || parsed.type;
42
+ continue;
43
+ }
44
+ if (arg.startsWith('--owner=')) {
45
+ parsed.owner = arg.split('=')[1] || null;
46
+ continue;
47
+ }
48
+ if (!parsed.storyId) {
49
+ parsed.storyId = arg;
50
+ continue;
51
+ }
52
+ if (!parsed.slug) {
53
+ parsed.slug = arg;
54
+ }
55
+ }
56
+
57
+ return parsed;
58
+ }
59
+
60
+ function resolveDefaultBranch() {
61
+ try {
62
+ const remoteHead = execGit('git symbolic-ref --short refs/remotes/origin/HEAD');
63
+ return remoteHead.replace(/^origin\//, '');
64
+ } catch {
65
+ return 'main';
66
+ }
67
+ }
68
+
69
+ function getCurrentBranch() {
70
+ return execGit('git rev-parse --abbrev-ref HEAD');
71
+ }
72
+
73
+ function getStatusLines() {
74
+ const output = safeExec('git status --porcelain');
75
+ return output ? output.split('\n').filter(Boolean) : [];
76
+ }
77
+
78
+ function sanitizeSegment(value) {
79
+ return String(value || '')
80
+ .trim()
81
+ .toLowerCase()
82
+ .replace(/[^a-z0-9]+/g, '-')
83
+ .replace(/^-+|-+$/g, '')
84
+ .replace(/-{2,}/g, '-');
85
+ }
86
+
87
+ function detectOwnerPrefix(explicitOwner) {
88
+ if (explicitOwner) {
89
+ return sanitizeSegment(explicitOwner) || 'dev';
90
+ }
91
+
92
+ const rawSignals = [
93
+ safeExec('git config user.name'),
94
+ safeExec('git config user.email'),
95
+ process.env.USERNAME || '',
96
+ process.env.USER || '',
97
+ ]
98
+ .join(' ')
99
+ .toLowerCase();
100
+
101
+ if (/(caio|imori)/.test(rawSignals)) {
102
+ return 'caio';
103
+ }
104
+
105
+ if (/(matheus|soier|sawyer)/.test(rawSignals)) {
106
+ return 'soier';
107
+ }
108
+
109
+ return 'dev';
110
+ }
111
+
112
+ function buildWorkItemSlug(storyId, slug) {
113
+ const sanitizedStory = sanitizeSegment(storyId);
114
+ const sanitizedSlug = sanitizeSegment(slug || storyId);
115
+ if (!sanitizedSlug) {
116
+ return sanitizedStory;
117
+ }
118
+ if (!sanitizedStory) {
119
+ return sanitizedSlug;
120
+ }
121
+ return sanitizedSlug.startsWith(`${sanitizedStory}-`) || sanitizedSlug === sanitizedStory
122
+ ? sanitizedSlug
123
+ : `${sanitizedStory}-${sanitizedSlug}`;
124
+ }
125
+
126
+ function buildBranchName({ owner, type, storyId, slug }) {
127
+ return `${owner}/${sanitizeSegment(type) || 'feat'}/${buildWorkItemSlug(storyId, slug)}`;
128
+ }
129
+
130
+ function buildWorktreePath(worktreeKey) {
131
+ return path.join(process.cwd(), '.sinapse', 'worktrees', worktreeKey);
132
+ }
133
+
134
+ function ensureReadyDefaultBranch(defaultBranch) {
135
+ const branchName = getCurrentBranch();
136
+ const statusLines = getStatusLines();
137
+
138
+ if (branchName !== defaultBranch) {
139
+ throw new Error(
140
+ `Run collab:start from a clean '${defaultBranch}' checkout. Current branch: '${branchName}'.`,
141
+ );
142
+ }
143
+
144
+ if (statusLines.length > 0) {
145
+ throw new Error(
146
+ `Working tree is dirty on '${defaultBranch}'. Commit or stash your changes before opening a new worktree.`,
147
+ );
148
+ }
149
+ }
150
+
151
+ function ensureOnDefaultBranch(defaultBranch) {
152
+ const branchName = getCurrentBranch();
153
+ if (branchName !== defaultBranch) {
154
+ throw new Error(
155
+ `Run this command from '${defaultBranch}'. Current branch: '${branchName}'.`,
156
+ );
157
+ }
158
+ }
159
+
160
+ function ensureUpToDate(defaultBranch) {
161
+ execFileSync('git', ['fetch', 'origin'], {
162
+ stdio: ['ignore', 'ignore', 'pipe'],
163
+ });
164
+
165
+ const localSha = safeExec(`git rev-parse ${defaultBranch}`);
166
+ const remoteSha = safeExec(`git rev-parse origin/${defaultBranch}`);
167
+
168
+ if (localSha && remoteSha && localSha !== remoteSha) {
169
+ execFileSync('git', ['pull', '--ff-only', 'origin', defaultBranch], {
170
+ stdio: 'inherit',
171
+ });
172
+ }
173
+ }
174
+
175
+ function createWorktree({ branchName, defaultBranch, worktreePath }) {
176
+ execFileSync('git', ['worktree', 'add', worktreePath, '-b', branchName, defaultBranch], {
177
+ stdio: 'inherit',
178
+ });
179
+ }
180
+
181
+ function adoptCurrentWorkspace(branchName) {
182
+ execFileSync('git', ['checkout', '-b', branchName], {
183
+ stdio: 'inherit',
184
+ });
185
+ }
186
+
187
+ function runCheck() {
188
+ const defaultBranch = resolveDefaultBranch();
189
+ const currentBranch = getCurrentBranch();
190
+ const statusLines = getStatusLines();
191
+ const owner = detectOwnerPrefix(null);
192
+
193
+ console.log(`Default branch: ${defaultBranch}`);
194
+ console.log(`Current branch: ${currentBranch}`);
195
+ console.log(`Detected maintainer prefix: ${owner}`);
196
+ console.log(`Working tree clean: ${statusLines.length === 0 ? 'yes' : 'no'}`);
197
+ }
198
+
199
+ function main() {
200
+ const args = parseArgs(process.argv.slice(2));
201
+ if (args.check) {
202
+ runCheck();
203
+ return;
204
+ }
205
+
206
+ if (!args.storyId) {
207
+ console.error(
208
+ 'Usage: npm run collab:start -- <story-id> <slug> [--type=feat] [--owner=caio] [--adopt-current]',
209
+ );
210
+ process.exit(1);
211
+ }
212
+
213
+ const defaultBranch = resolveDefaultBranch();
214
+ const owner = detectOwnerPrefix(args.owner);
215
+ const branchName = buildBranchName({
216
+ owner,
217
+ type: args.type,
218
+ storyId: args.storyId,
219
+ slug: args.slug,
220
+ });
221
+ const worktreeKey = sanitizeSegment(branchName.replace(/\//g, '-'));
222
+ const worktreePath = buildWorktreePath(worktreeKey);
223
+
224
+ try {
225
+ if (args.adoptCurrent) {
226
+ ensureOnDefaultBranch(defaultBranch);
227
+ adoptCurrentWorkspace(branchName);
228
+ } else {
229
+ ensureReadyDefaultBranch(defaultBranch);
230
+ ensureUpToDate(defaultBranch);
231
+ createWorktree({ branchName, defaultBranch, worktreePath });
232
+ }
233
+ } catch (error) {
234
+ console.error('');
235
+ console.error(`collab:start failed: ${error.message}`);
236
+ console.error('');
237
+ process.exit(1);
238
+ }
239
+
240
+ console.log('');
241
+ if (args.adoptCurrent) {
242
+ console.log('Current workspace adopted into a safe maintainer branch.');
243
+ console.log(`Branch: ${branchName}`);
244
+ console.log('');
245
+ console.log('Continue working in the current directory, now outside main.');
246
+ } else {
247
+ console.log('Safe maintainer workspace created.');
248
+ console.log(`Branch: ${branchName}`);
249
+ console.log(`Worktree: ${worktreePath}`);
250
+ console.log('');
251
+ console.log(`Next step: cd "${worktreePath}"`);
252
+ }
253
+ console.log('');
254
+ }
255
+
256
+ module.exports = {
257
+ buildBranchName,
258
+ buildWorkItemSlug,
259
+ detectOwnerPrefix,
260
+ parseArgs,
261
+ resolveDefaultBranch,
262
+ sanitizeSegment,
263
+ };
264
+
265
+ if (require.main === module) {
266
+ main();
267
+ }
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const { execSync } = require('child_process');
4
+
5
+ function execGit(command, options = {}) {
6
+ return execSync(command, {
7
+ encoding: 'utf8',
8
+ stdio: ['ignore', 'pipe', 'pipe'],
9
+ ...options,
10
+ }).trim();
11
+ }
12
+
13
+ function resolveDefaultBranch() {
14
+ try {
15
+ const remoteHead = execGit('git symbolic-ref --short refs/remotes/origin/HEAD');
16
+ return remoteHead.replace(/^origin\//, '');
17
+ } catch {
18
+ return 'main';
19
+ }
20
+ }
21
+
22
+ function getCurrentBranch() {
23
+ return execGit('git rev-parse --abbrev-ref HEAD');
24
+ }
25
+
26
+ function getStagedFiles() {
27
+ try {
28
+ const output = execGit('git diff --cached --name-only');
29
+ return output ? output.split('\n').filter(Boolean) : [];
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ function shouldBlockCommit({ branchName, defaultBranch, stagedFiles }) {
36
+ const protectedBranches = new Set([defaultBranch, 'master']);
37
+ return Boolean(stagedFiles.length) && (protectedBranches.has(branchName) || branchName === 'HEAD');
38
+ }
39
+
40
+ function main() {
41
+ const defaultBranch = resolveDefaultBranch();
42
+ const branchName = getCurrentBranch();
43
+ const stagedFiles = getStagedFiles();
44
+
45
+ if (!shouldBlockCommit({ branchName, defaultBranch, stagedFiles })) {
46
+ process.exit(0);
47
+ }
48
+
49
+ console.error('');
50
+ console.error('Git Branch Guard: commit blocked.');
51
+ console.error('');
52
+ if (branchName === 'HEAD') {
53
+ console.error('You are in detached HEAD state. Create an isolated branch or worktree first.');
54
+ } else {
55
+ console.error(`Commits are blocked on the protected branch '${branchName}'.`);
56
+ }
57
+ console.error('');
58
+ console.error('Start a safe maintainer workspace first:');
59
+ console.error(' npm run collab:start -- <story-id> <slug>');
60
+ console.error('');
61
+ console.error('Example:');
62
+ console.error(' npm run collab:start -- 7.7.4 codex-collab-hardening');
63
+ console.error('');
64
+ process.exit(1);
65
+ }
66
+
67
+ module.exports = {
68
+ getCurrentBranch,
69
+ getStagedFiles,
70
+ resolveDefaultBranch,
71
+ shouldBlockCommit,
72
+ };
73
+
74
+ if (require.main === module) {
75
+ main();
76
+ }
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync, execSync, spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+
6
+ function execGit(command) {
7
+ return execSync(command, {
8
+ encoding: 'utf8',
9
+ stdio: ['ignore', 'pipe', 'pipe'],
10
+ }).trim();
11
+ }
12
+
13
+ function resolveDefaultBranch() {
14
+ try {
15
+ const remoteHead = execGit('git symbolic-ref --short refs/remotes/origin/HEAD');
16
+ return remoteHead.replace(/^origin\//, '');
17
+ } catch {
18
+ return 'main';
19
+ }
20
+ }
21
+
22
+ function getCurrentBranch() {
23
+ return execGit('git rev-parse --abbrev-ref HEAD');
24
+ }
25
+
26
+ function parsePushRefs(stdinBuffer) {
27
+ const payload = stdinBuffer.toString('utf8').trim();
28
+ if (!payload) {
29
+ return [];
30
+ }
31
+
32
+ return payload
33
+ .split('\n')
34
+ .filter(Boolean)
35
+ .map((line) => {
36
+ const [localRef, localSha, remoteRef, remoteSha] = line.trim().split(/\s+/);
37
+ return { localRef, localSha, remoteRef, remoteSha };
38
+ });
39
+ }
40
+
41
+ function isProtectedPushTarget(remoteRef, defaultBranch) {
42
+ return remoteRef === `refs/heads/${defaultBranch}` || remoteRef === 'refs/heads/master';
43
+ }
44
+
45
+ function branchContainsRemoteDefault(defaultBranch) {
46
+ const result = spawnSync('git', ['merge-base', '--is-ancestor', `origin/${defaultBranch}`, 'HEAD'], {
47
+ stdio: 'ignore',
48
+ });
49
+ return result.status === 0;
50
+ }
51
+
52
+ function fetchRemoteDefault(defaultBranch) {
53
+ execFileSync('git', ['fetch', 'origin', defaultBranch, '--quiet'], {
54
+ stdio: ['ignore', 'ignore', 'pipe'],
55
+ });
56
+ }
57
+
58
+ function main() {
59
+ const defaultBranch = resolveDefaultBranch();
60
+ const branchName = getCurrentBranch();
61
+ const refs = parsePushRefs(fs.readFileSync(0));
62
+
63
+ if (branchName === defaultBranch || branchName === 'master') {
64
+ console.error('');
65
+ console.error(`Pre-push Safety: push blocked on protected branch '${branchName}'.`);
66
+ console.error('Open a feature branch or worktree first.');
67
+ console.error('');
68
+ process.exit(1);
69
+ }
70
+
71
+ if (refs.some((ref) => isProtectedPushTarget(ref.remoteRef, defaultBranch))) {
72
+ console.error('');
73
+ console.error(`Pre-push Safety: direct push to '${defaultBranch}' is blocked.`);
74
+ console.error('Open a pull request instead.');
75
+ console.error('');
76
+ process.exit(1);
77
+ }
78
+
79
+ try {
80
+ fetchRemoteDefault(defaultBranch);
81
+ } catch {
82
+ console.error('');
83
+ console.error(`Pre-push Safety: could not fetch origin/${defaultBranch}.`);
84
+ console.error('Sync with the remote before pushing.');
85
+ console.error('');
86
+ process.exit(1);
87
+ }
88
+
89
+ if (!branchContainsRemoteDefault(defaultBranch)) {
90
+ console.error('');
91
+ console.error(`Pre-push Safety: your branch is missing commits from origin/${defaultBranch}.`);
92
+ console.error(`Merge or rebase origin/${defaultBranch} before pushing.`);
93
+ console.error('');
94
+ process.exit(1);
95
+ }
96
+
97
+ process.exit(0);
98
+ }
99
+
100
+ module.exports = {
101
+ branchContainsRemoteDefault,
102
+ getCurrentBranch,
103
+ isProtectedPushTarget,
104
+ parsePushRefs,
105
+ resolveDefaultBranch,
106
+ };
107
+
108
+ if (require.main === module) {
109
+ main();
110
+ }