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.
- package/.claude/hooks/enforce-git-push-authority.sh +34 -2
- package/.claude/rules/safe-collaboration.md +12 -1
- package/.codex/catalog.json +157 -0
- package/.codex/command-registry.json +441 -0
- package/.codex/scripts/generate-codex-greeting.js +101 -0
- package/.codex/scripts/resolve-codex-command.js +147 -0
- package/.codex/skills/sinapse-analyst/SKILL.md +5 -4
- package/.codex/skills/sinapse-architect/SKILL.md +5 -4
- package/.codex/skills/sinapse-data-engineer/SKILL.md +5 -4
- package/.codex/skills/sinapse-dev/SKILL.md +5 -4
- package/.codex/skills/sinapse-devops/SKILL.md +5 -4
- package/.codex/skills/sinapse-orqx/SKILL.md +10 -15
- package/.codex/skills/sinapse-pm/SKILL.md +5 -4
- package/.codex/skills/sinapse-po/SKILL.md +4 -3
- package/.codex/skills/sinapse-qa/SKILL.md +12 -11
- package/.codex/skills/sinapse-sm/SKILL.md +5 -4
- package/.codex/skills/sinapse-squad-creator/SKILL.md +5 -4
- package/.codex/skills/sinapse-ux-design-expert/SKILL.md +5 -4
- package/.codex/tasks/convene-sinapse-council.md +28 -0
- package/.codex/tasks/create-sinapse-strategic-brief.md +29 -0
- package/.codex/tasks/onboard-sinapse-codex.md +34 -0
- package/.codex/tasks/plan-sinapse-initiative.md +33 -0
- package/.codex/tasks/resolve-sinapse-conflict.md +28 -0
- package/.codex/tasks/route-sinapse-request.md +33 -0
- package/.codex/tasks/status-sinapse-capabilities.md +28 -0
- package/.sinapse-ai/core-config.yaml +1 -1
- package/.sinapse-ai/data/entity-registry.yaml +903 -805
- package/.sinapse-ai/data/registry-update-log.jsonl +10 -0
- package/.sinapse-ai/infrastructure/scripts/codex-parity/catalog.js +123 -0
- package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/index.js +60 -11
- package/.sinapse-ai/infrastructure/scripts/codex-skills-sync/validate.js +44 -16
- package/.sinapse-ai/infrastructure/scripts/sync-codex-local-first.js +156 -0
- package/.sinapse-ai/infrastructure/scripts/validate-codex-command-registry.js +264 -0
- package/.sinapse-ai/infrastructure/scripts/validate-codex-integration.js +15 -6
- package/.sinapse-ai/infrastructure/scripts/validate-codex-sync.js +156 -0
- package/.sinapse-ai/infrastructure/scripts/validate-parity.js +3 -1
- package/.sinapse-ai/infrastructure/scripts/validate-paths.js +8 -10
- package/.sinapse-ai/infrastructure/templates/safe-collab/README.md +52 -17
- package/.sinapse-ai/infrastructure/templates/safe-collab/apply.sh +85 -0
- package/.sinapse-ai/infrastructure/templates/safe-collab/safe-collaboration-rule.md +11 -0
- package/.sinapse-ai/install-manifest.yaml +41 -21
- package/.sinapse-ai/project-config.yaml +1 -1
- package/bin/utils/collab-start.js +267 -0
- package/bin/utils/git-branch-guard.js +76 -0
- package/bin/utils/pre-push-safety.js +110 -0
- package/bin/utils/staged-secret-scan.js +108 -0
- package/docs/ORQX-PLAN.md +3 -2
- package/docs/codex-parity-program.md +670 -0
- package/docs/codex-total-parity-orchestration-plan.md +301 -0
- package/docs/codex-workflow-task-parity.md +87 -0
- package/docs/collaboration-autonomy-plan.md +243 -0
- package/docs/guides/framework-contributor-mode.md +310 -0
- package/docs/guides/parallel-collaboration-source-of-truth.md +481 -0
- package/package.json +11 -3
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +2 -2
- 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.
|
|
11
|
-
generated_at: "2026-04-
|
|
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:
|
|
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:
|
|
192
|
+
hash: sha256:fc24a8286b20ec2558c90186e99124b96d26b287ff107b747c2b68b1102e0cc8
|
|
193
193
|
type: config
|
|
194
|
-
size:
|
|
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:
|
|
1240
|
+
hash: sha256:265b44f0e9c36e85375f05ba88dbf19ed9033244a6eea338f4b8e3a4e453dd12
|
|
1241
1241
|
type: data
|
|
1242
|
-
size:
|
|
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:
|
|
3060
|
+
hash: sha256:9eb2637a4f1f6edeabf358734b14e4c709bed37560f2e40aebf1ea3ef4572c3c
|
|
3057
3061
|
type: script
|
|
3058
|
-
size:
|
|
3062
|
+
size: 7494
|
|
3059
3063
|
- path: infrastructure/scripts/codex-skills-sync/validate.js
|
|
3060
|
-
hash: sha256:
|
|
3064
|
+
hash: sha256:1a1301d2e3f558bedf3a1ab2c5c9ee2868a4c253068311d5014987917f367443
|
|
3061
3065
|
type: script
|
|
3062
|
-
size:
|
|
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:
|
|
3436
|
+
hash: sha256:c553cc493ddbfecf41b865f8a1075df56b6500c3b2b315bae8b7e7bcec4db85a
|
|
3425
3437
|
type: script
|
|
3426
|
-
size:
|
|
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:
|
|
3448
|
+
hash: sha256:284c7715cc65b76579284024bbe5176b9230340d841dbfe79559c8f5844109cb
|
|
3433
3449
|
type: script
|
|
3434
|
-
size:
|
|
3450
|
+
size: 12266
|
|
3435
3451
|
- path: infrastructure/scripts/validate-paths.js
|
|
3436
|
-
hash: sha256:
|
|
3452
|
+
hash: sha256:942f6de9e621b7331f61dc915db5d24b987b67d1ae3377882330f7013dd214e9
|
|
3437
3453
|
type: script
|
|
3438
|
-
size:
|
|
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:
|
|
3544
|
+
hash: sha256:0b4f3d0ce9b7111d812917312ae504df976245d902eecfa501f73e7aa0719813
|
|
3525
3545
|
type: template
|
|
3526
|
-
size:
|
|
3546
|
+
size: 2179
|
|
3527
3547
|
- path: infrastructure/templates/safe-collab/safe-collaboration-rule.md
|
|
3528
|
-
hash: sha256:
|
|
3548
|
+
hash: sha256:9631681340fddc314080f9182b76ccfbb1ee3441d92ce181067e76cace960a5e
|
|
3529
3549
|
type: template
|
|
3530
|
-
size:
|
|
3550
|
+
size: 2871
|
|
3531
3551
|
- path: infrastructure/templates/sinapse-sync.yaml.template
|
|
3532
3552
|
hash: sha256:36ada7873fcbd5bab225b528c7be39035a12fc156135cc82c73d75782d7c53a2
|
|
3533
3553
|
type: template
|
|
@@ -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
|
+
}
|