@warpgogol/forge 5.2.0 → 5.2.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/AGENTS.md +4 -1
- package/dist/os/adr/adr.module.js +1 -1
- package/dist/os/adr/adr.module.js.map +1 -1
- package/dist/os/compass/compass.module.js +2 -2
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.d.ts +3 -0
- package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.js +87 -8
- package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +15 -1
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +22 -6
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/program/lease.js +1 -1
- package/dist/os/program/lease.js.map +1 -1
- package/dist/os/queue/queue.module.js +1 -1
- package/dist/os/queue/queue.module.js.map +1 -1
- package/dist/os/rfc/handlers/list-create.d.ts.map +1 -1
- package/dist/os/rfc/handlers/list-create.js +79 -32
- package/dist/os/rfc/handlers/list-create.js.map +1 -1
- package/dist/os/rfc/rfc.module.js +1 -1
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/spec/spec.module.d.ts.map +1 -1
- package/dist/os/spec/spec.module.js +2 -0
- package/dist/os/spec/spec.module.js.map +1 -1
- package/dist/src/config/forge-config.d.ts +9 -0
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +7 -0
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/knowledge/index.d.ts +1 -0
- package/dist/src/knowledge/index.d.ts.map +1 -1
- package/dist/src/knowledge/index.js +1 -0
- package/dist/src/knowledge/index.js.map +1 -1
- package/dist/src/knowledge/sync.d.ts +24 -0
- package/dist/src/knowledge/sync.d.ts.map +1 -0
- package/dist/src/knowledge/sync.js +127 -0
- package/dist/src/knowledge/sync.js.map +1 -0
- package/dist/src/onboarding/agents-generate.d.ts +2 -0
- package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/agents-generate.js +14 -18
- package/dist/src/onboarding/agents-generate.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts +6 -0
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +124 -14
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +15 -8
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/memory-compact.d.ts +22 -0
- package/dist/src/onboarding/memory-compact.d.ts.map +1 -0
- package/dist/src/onboarding/memory-compact.js +147 -0
- package/dist/src/onboarding/memory-compact.js.map +1 -0
- package/dist/src/onboarding/memory-scaffold.d.ts +1 -0
- package/dist/src/onboarding/memory-scaffold.d.ts.map +1 -1
- package/dist/src/onboarding/memory-scaffold.js +1 -1
- package/dist/src/onboarding/memory-scaffold.js.map +1 -1
- package/dist/src/onboarding/nested-agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/nested-agents-generate.js +1 -1
- package/dist/src/onboarding/nested-agents-generate.js.map +1 -1
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +42 -13
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/dist/src/onboarding/workspace-discovery.d.ts +1 -1
- package/dist/src/onboarding/workspace-discovery.d.ts.map +1 -1
- package/dist/src/onboarding/workspace-discovery.js +14 -2
- package/dist/src/onboarding/workspace-discovery.js.map +1 -1
- package/dist/src/types.d.ts +1 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/fs-atomic.js +1 -1
- package/dist/src/utils/fs-atomic.js.map +1 -1
- package/docs/reference/forge-yaml.md +12 -0
- package/os/adr/adr.module.ts +1 -1
- package/os/compass/compass.module.ts +2 -2
- package/os/compass/handlers/compass-audit-handler.ts +116 -8
- package/os/compass/handlers/tests/compass-audit-plan.test.ts +98 -0
- package/os/compass/handlers/tests/compass-audit-record.test.ts +105 -0
- package/os/compass/handlers/tests/compass-audit-validate.test.ts +133 -0
- package/os/compass/handlers/tests/compass-ledger-scope.test.ts +124 -0
- package/os/core/core.module.ts +16 -1
- package/os/mission/handlers/archive.test.ts +49 -0
- package/os/mission/handlers/archive.ts +21 -6
- package/os/program/lease.ts +1 -1
- package/os/queue/queue.module.ts +1 -1
- package/os/rfc/handlers/list-create.ts +83 -35
- package/os/rfc/handlers/validate-rules.test.ts +50 -0
- package/os/rfc/rfc-create-concurrent.test.ts +118 -0
- package/os/rfc/rfc-create-hint.test.ts +90 -0
- package/os/rfc/rfc-read-only-no-side-effects.test.ts +83 -0
- package/os/rfc/rfc.module.ts +1 -1
- package/os/spec/spec.module.ts +2 -0
- package/package.json +1 -1
- package/profiles/forge-shell.yaml +3 -0
- package/profiles/godot-game.yaml +3 -0
- package/profiles/phaser-game.yaml +3 -0
- package/profiles/site-workshop.yaml +567 -564
- package/src/config/forge-config.ts +11 -0
- package/src/knowledge/__tests__/sync.test.ts +227 -0
- package/src/knowledge/index.ts +7 -0
- package/src/knowledge/sync.ts +161 -0
- package/src/onboarding/agents-generate.ts +18 -18
- package/src/onboarding/doctor.ts +141 -15
- package/src/onboarding/init.ts +15 -8
- package/src/onboarding/memory-compact.ts +188 -0
- package/src/onboarding/memory-scaffold.ts +1 -1
- package/src/onboarding/nested-agents-generate.ts +5 -1
- package/src/onboarding/upgrade.ts +47 -16
- package/src/onboarding/workspace-discovery.ts +15 -1
- package/src/tests/agents-generate.test.ts +26 -3
- package/src/tests/doctor-dist-freshness.test.ts +110 -0
- package/src/tests/doctor-fix.test.ts +145 -0
- package/src/tests/doctor-nested-agents.test.ts +108 -0
- package/src/tests/memory-compact.test.ts +143 -0
- package/src/tests/upgrade.test.ts +53 -0
- package/src/tests/workspace-discovery.test.ts +28 -0
- package/src/types.ts +2 -2
- package/src/utils/fs-atomic.ts +1 -1
|
@@ -3,609 +3,612 @@ id: site-workshop
|
|
|
3
3
|
displayName: Werkstatt Site Workshop
|
|
4
4
|
detect:
|
|
5
5
|
anyOf:
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
- werkstatt.identity.json
|
|
7
|
+
- missions/.gitkeep
|
|
8
8
|
workspace:
|
|
9
9
|
dirs:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
- docs
|
|
11
|
+
- scripts
|
|
12
|
+
- tools
|
|
13
|
+
- missions
|
|
14
14
|
files:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
- path: .gitignore
|
|
26
|
-
content: |
|
|
27
|
-
node_modules
|
|
28
|
-
dist
|
|
29
|
-
.turbo
|
|
30
|
-
.cache
|
|
31
|
-
operator-profile.md
|
|
32
|
-
- path: .github/workflows/ci.yml
|
|
33
|
-
content: |
|
|
34
|
-
name: CI
|
|
35
|
-
|
|
36
|
-
on:
|
|
37
|
-
pull_request:
|
|
38
|
-
push:
|
|
39
|
-
branches: [main]
|
|
40
|
-
workflow_dispatch:
|
|
41
|
-
|
|
42
|
-
concurrency:
|
|
43
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
44
|
-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
45
|
-
|
|
46
|
-
permissions:
|
|
47
|
-
contents: read
|
|
48
|
-
|
|
49
|
-
jobs:
|
|
50
|
-
ci:
|
|
51
|
-
runs-on: ubuntu-latest
|
|
52
|
-
timeout-minutes: 15
|
|
53
|
-
name: Lint, type-check, test
|
|
54
|
-
env:
|
|
55
|
-
TZ: UTC
|
|
56
|
-
|
|
57
|
-
steps:
|
|
58
|
-
- name: Checkout
|
|
59
|
-
uses: actions/checkout@v5
|
|
60
|
-
|
|
61
|
-
- name: Enable Corepack
|
|
62
|
-
run: corepack enable
|
|
63
|
-
|
|
64
|
-
- name: Setup Node.js
|
|
65
|
-
uses: actions/setup-node@v5
|
|
66
|
-
with:
|
|
67
|
-
node-version: "24"
|
|
68
|
-
cache: "pnpm"
|
|
69
|
-
|
|
70
|
-
- name: Install dependencies
|
|
71
|
-
run: pnpm install --frozen-lockfile
|
|
72
|
-
|
|
73
|
-
- name: Format check
|
|
74
|
-
run: pnpm format:check
|
|
75
|
-
|
|
76
|
-
- name: Test
|
|
77
|
-
run: pnpm test
|
|
78
|
-
|
|
79
|
-
# Windows CI (optional — uncomment if Windows checks are needed):
|
|
80
|
-
#
|
|
81
|
-
# windows-ci:
|
|
82
|
-
# runs-on: windows-latest
|
|
83
|
-
# name: Lint, type-check, test (Windows)
|
|
84
|
-
# env:
|
|
85
|
-
# GIT_CONFIG_COUNT: 1
|
|
86
|
-
# GIT_CONFIG_KEY_0: core.longpaths
|
|
87
|
-
# GIT_CONFIG_VALUE_0: "true"
|
|
88
|
-
# steps:
|
|
89
|
-
# - name: Checkout
|
|
90
|
-
# uses: actions/checkout@v5
|
|
91
|
-
# - name: Enable Corepack
|
|
92
|
-
# run: corepack enable
|
|
93
|
-
# - name: Setup Node.js
|
|
94
|
-
# uses: actions/setup-node@v5
|
|
95
|
-
# with:
|
|
96
|
-
# node-version: "24"
|
|
97
|
-
# cache: "pnpm"
|
|
98
|
-
# - name: Install dependencies
|
|
99
|
-
# run: pnpm install --frozen-lockfile
|
|
100
|
-
# - name: Format check
|
|
101
|
-
# run: pnpm format:check
|
|
102
|
-
# - name: Test
|
|
103
|
-
# run: pnpm test
|
|
104
|
-
#
|
|
105
|
-
# The GIT_CONFIG_COUNT env block MUST be set before the checkout step
|
|
106
|
-
# so git can handle long file paths during clone on Windows.
|
|
107
|
-
# Do not add Windows to the matrix "just in case" — it requires
|
|
108
|
-
# separate path-length and platform-specific dependency verification.
|
|
109
|
-
- path: package.json
|
|
110
|
-
content: |
|
|
111
|
-
{
|
|
112
|
-
"name": "__PROJECT_NAME__",
|
|
113
|
-
"version": "0.1.0",
|
|
114
|
-
"private": true,
|
|
115
|
-
"type": "module",
|
|
116
|
-
"engines": {
|
|
117
|
-
"node": ">=24 <25"
|
|
118
|
-
},
|
|
119
|
-
"scripts": {
|
|
120
|
-
"clean": "node scripts/clean.mjs",
|
|
121
|
-
"format": "prettier --write .",
|
|
122
|
-
"format:check": "prettier --check .",
|
|
123
|
-
"test": "vitest run --passWithNoTests",
|
|
124
|
-
"upgrade-packages": "pnpm up"
|
|
15
|
+
- path: .vscode/extensions.json
|
|
16
|
+
content: |
|
|
17
|
+
{
|
|
18
|
+
"recommendations": [
|
|
19
|
+
"dbaeumer.vscode-eslint",
|
|
20
|
+
"esbenp.prettier-vscode",
|
|
21
|
+
"github.vscode-github-actions",
|
|
22
|
+
"mechatroner.rainbow-csv"
|
|
23
|
+
]
|
|
125
24
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
25
|
+
- path: .gitignore
|
|
26
|
+
content: |
|
|
27
|
+
node_modules
|
|
28
|
+
dist
|
|
29
|
+
.turbo
|
|
30
|
+
.cache
|
|
31
|
+
operator-profile.md
|
|
32
|
+
- path: .github/workflows/ci.yml
|
|
33
|
+
content: |
|
|
34
|
+
name: CI
|
|
35
|
+
|
|
36
|
+
on:
|
|
37
|
+
pull_request:
|
|
38
|
+
push:
|
|
39
|
+
branches: [main]
|
|
40
|
+
workflow_dispatch:
|
|
41
|
+
|
|
42
|
+
concurrency:
|
|
43
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
44
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
45
|
+
|
|
46
|
+
permissions:
|
|
47
|
+
contents: read
|
|
48
|
+
|
|
49
|
+
jobs:
|
|
50
|
+
ci:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
name: Lint, type-check, test
|
|
54
|
+
env:
|
|
55
|
+
TZ: UTC
|
|
56
|
+
|
|
57
|
+
steps:
|
|
58
|
+
- name: Checkout
|
|
59
|
+
uses: actions/checkout@v5
|
|
60
|
+
|
|
61
|
+
- name: Enable Corepack
|
|
62
|
+
run: corepack enable
|
|
63
|
+
|
|
64
|
+
- name: Setup Node.js
|
|
65
|
+
uses: actions/setup-node@v5
|
|
66
|
+
with:
|
|
67
|
+
node-version: "24"
|
|
68
|
+
cache: "pnpm"
|
|
69
|
+
|
|
70
|
+
- name: Install dependencies
|
|
71
|
+
run: pnpm install --frozen-lockfile
|
|
72
|
+
|
|
73
|
+
- name: Forge doctor
|
|
74
|
+
run: pnpm exec forge run forge.doctor
|
|
75
|
+
|
|
76
|
+
- name: Format check
|
|
77
|
+
run: pnpm format:check
|
|
78
|
+
|
|
79
|
+
- name: Test
|
|
80
|
+
run: pnpm test
|
|
81
|
+
|
|
82
|
+
# Windows CI (optional — uncomment if Windows checks are needed):
|
|
83
|
+
#
|
|
84
|
+
# windows-ci:
|
|
85
|
+
# runs-on: windows-latest
|
|
86
|
+
# name: Lint, type-check, test (Windows)
|
|
87
|
+
# env:
|
|
88
|
+
# GIT_CONFIG_COUNT: 1
|
|
89
|
+
# GIT_CONFIG_KEY_0: core.longpaths
|
|
90
|
+
# GIT_CONFIG_VALUE_0: "true"
|
|
91
|
+
# steps:
|
|
92
|
+
# - name: Checkout
|
|
93
|
+
# uses: actions/checkout@v5
|
|
94
|
+
# - name: Enable Corepack
|
|
95
|
+
# run: corepack enable
|
|
96
|
+
# - name: Setup Node.js
|
|
97
|
+
# uses: actions/setup-node@v5
|
|
98
|
+
# with:
|
|
99
|
+
# node-version: "24"
|
|
100
|
+
# cache: "pnpm"
|
|
101
|
+
# - name: Install dependencies
|
|
102
|
+
# run: pnpm install --frozen-lockfile
|
|
103
|
+
# - name: Format check
|
|
104
|
+
# run: pnpm format:check
|
|
105
|
+
# - name: Test
|
|
106
|
+
# run: pnpm test
|
|
107
|
+
#
|
|
108
|
+
# The GIT_CONFIG_COUNT env block MUST be set before the checkout step
|
|
109
|
+
# so git can handle long file paths during clone on Windows.
|
|
110
|
+
# Do not add Windows to the matrix "just in case" — it requires
|
|
111
|
+
# separate path-length and platform-specific dependency verification.
|
|
112
|
+
- path: package.json
|
|
113
|
+
content: |
|
|
114
|
+
{
|
|
115
|
+
"name": "__PROJECT_NAME__",
|
|
116
|
+
"version": "0.1.0",
|
|
117
|
+
"private": true,
|
|
118
|
+
"type": "module",
|
|
119
|
+
"engines": {
|
|
120
|
+
"node": ">=24 <25"
|
|
121
|
+
},
|
|
122
|
+
"scripts": {
|
|
123
|
+
"clean": "node scripts/clean.mjs",
|
|
124
|
+
"format": "prettier --write .",
|
|
125
|
+
"format:check": "prettier --check .",
|
|
126
|
+
"test": "vitest run --passWithNoTests",
|
|
127
|
+
"upgrade-packages": "pnpm up"
|
|
146
128
|
}
|
|
147
|
-
await walk(fullPath, toDelete);
|
|
148
129
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
130
|
+
- path: scripts/clean.mjs
|
|
131
|
+
content: |
|
|
132
|
+
import { readdir, rm } from "node:fs/promises";
|
|
133
|
+
import { join } from "node:path";
|
|
134
|
+
import { fileURLToPath } from "node:url";
|
|
135
|
+
|
|
136
|
+
const TARGETS = new Set(["node_modules", ".turbo", "dist", ".astro", ".cache", ".wrangler"]);
|
|
137
|
+
|
|
138
|
+
const repoRoot = join(fileURLToPath(import.meta.url), "..", "..");
|
|
139
|
+
|
|
140
|
+
async function walk(dir, toDelete) {
|
|
141
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.isDirectory()) continue;
|
|
144
|
+
const name = entry.name;
|
|
145
|
+
const fullPath = join(dir, name);
|
|
146
|
+
if (TARGETS.has(name)) {
|
|
147
|
+
toDelete.push(fullPath);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
await walk(fullPath, toDelete);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const toDelete = [];
|
|
155
|
+
await walk(repoRoot, toDelete);
|
|
156
|
+
|
|
157
|
+
if (toDelete.length === 0) {
|
|
158
|
+
console.log("Nothing to clean.");
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await Promise.all(toDelete.map((p) => rm(p, { recursive: true, force: true })));
|
|
163
|
+
|
|
164
|
+
for (const p of toDelete) {
|
|
165
|
+
console.log("deleted", p.replace(repoRoot + "/", ""));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(`Cleaned ${toDelete.length} directories.`)
|
|
169
|
+
- path: tools/kernel.config.ts
|
|
170
|
+
content: |
|
|
171
|
+
import { defineKernelConfig } from "@warpgogol/werkstatt-engine/kernel/types";
|
|
172
|
+
import { workshopModuleLoaders } from "@warpgogol/werkstatt-engine/kernel/module-map";
|
|
173
|
+
|
|
174
|
+
export default defineKernelConfig({
|
|
175
|
+
name: "__PROJECT_NAME__",
|
|
176
|
+
description: "__PROJECT_NAME__ workshop",
|
|
177
|
+
moduleLoaders: {
|
|
178
|
+
// RFC-1125: shared workshop subset — single-sourced in
|
|
179
|
+
// @warpgogol/werkstatt-engine/kernel/module-map. Do not add loaders here;
|
|
180
|
+
// new workshop-facing modules are added to WORKSHOP_MODULE_MAP once.
|
|
181
|
+
...workshopModuleLoaders(),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
- path: .npmrc
|
|
185
|
+
content: |
|
|
186
|
+
# @warpgogol scoped registry — replace YOUR_NPM_TOKEN with a valid npm read token
|
|
187
|
+
@warpgogol:registry=https://registry.npmjs.org/
|
|
188
|
+
//registry.npmjs.org/:_authToken=YOUR_NPM_TOKEN
|
|
189
|
+
# Auto-approve build scripts so operators aren't prompted during install
|
|
190
|
+
dangerously-allow-all-builds=true
|
|
191
|
+
- path: eslint.config.js
|
|
192
|
+
content: |
|
|
193
|
+
export default [
|
|
194
|
+
{
|
|
195
|
+
rules: {},
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
- path: .prettierrc.mjs
|
|
199
|
+
content: |
|
|
200
|
+
export default {
|
|
201
|
+
semi: true,
|
|
202
|
+
singleQuote: false,
|
|
203
|
+
trailingComma: "all",
|
|
204
|
+
printWidth: 100,
|
|
205
|
+
tabWidth: 2,
|
|
206
|
+
};
|
|
207
|
+
- path: .gitattributes
|
|
208
|
+
content: |
|
|
209
|
+
# No LFS patterns for this stack
|
|
210
|
+
- path: hooks/pre-commit
|
|
211
|
+
content: |
|
|
212
|
+
#!/bin/sh
|
|
213
|
+
# Platform-scope pre-commit guard
|
|
214
|
+
# Runs forge validation before commits
|
|
215
|
+
echo "Pre-commit: running platform checks..."
|
|
216
|
+
pnpm exec forge run forge.validate || exit 1
|
|
217
|
+
- path: scripts/git-guard.sh
|
|
218
|
+
content: |
|
|
219
|
+
#!/bin/bash
|
|
220
|
+
# git-guard.sh — shell function that intercepts destructive git commands.
|
|
221
|
+
# Sourced by ~/.zshenv (via setup-git-guards.sh).
|
|
222
|
+
# Guards (only active in repos containing scripts/git-guard.sh):
|
|
223
|
+
# git stash, git reset --hard, git checkout --, git checkout -f,
|
|
224
|
+
# git switch -f, git clean -f, git restore
|
|
225
|
+
# Override: ALLOW_DESTRUCTIVE_GIT=1 git stash
|
|
226
|
+
|
|
227
|
+
export FORGE_GIT_GUARD=1
|
|
228
|
+
|
|
229
|
+
_git_guard_find_root() {
|
|
230
|
+
local _repo_root _dir
|
|
231
|
+
_repo_root="$(command git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
232
|
+
if [ -z "$_repo_root" ]; then echo ""; return; fi
|
|
233
|
+
if [ -f "$_repo_root/scripts/git-guard.sh" ]; then echo "$_repo_root"; return; fi
|
|
234
|
+
_dir="$(dirname "$_repo_root")"
|
|
235
|
+
while [ -n "$_dir" ] && [ "$_dir" != "/" ]; do
|
|
236
|
+
if [ -f "$_dir/scripts/git-guard.sh" ]; then echo "$_dir"; return; fi
|
|
237
|
+
_dir="$(dirname "$_dir")"
|
|
238
|
+
done
|
|
239
|
+
echo ""
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
git() {
|
|
243
|
+
local _guard_root
|
|
244
|
+
_guard_root="$(_git_guard_find_root)"
|
|
245
|
+
if [ -z "$_guard_root" ] || [ -n "${ALLOW_DESTRUCTIVE_GIT:-}" ]; then
|
|
246
|
+
command git "$@"; return $?
|
|
247
|
+
fi
|
|
248
|
+
local _all_args=("$@") _i=0 _cmd="" _cmd_index=0
|
|
249
|
+
while [ "$_i" -lt "${#_all_args[@]}" ]; do
|
|
250
|
+
local _arg="${_all_args[$_i]}"
|
|
251
|
+
case "$_arg" in
|
|
252
|
+
-C|-c|--git-dir|--work-tree|--namespace) _i=$((_i + 2)); continue ;;
|
|
253
|
+
-*) _i=$((_i + 1)); continue ;;
|
|
254
|
+
esac
|
|
255
|
+
_cmd="$_arg"; _cmd_index="$_i"; break
|
|
256
|
+
done
|
|
257
|
+
local _sub_args=() _j=$((_cmd_index + 1))
|
|
258
|
+
while [ "$_j" -lt "${#_all_args[@]}" ]; do
|
|
259
|
+
_sub_args+=("${_all_args[$_j]}"); _j=$((_j + 1))
|
|
260
|
+
done
|
|
261
|
+
case "$_cmd" in
|
|
262
|
+
stash)
|
|
263
|
+
echo "BLOCKED: git stash is disabled in agent sessions." >&2
|
|
264
|
+
echo "Set ALLOW_DESTRUCTIVE_GIT=1 to override." >&2; return 1 ;;
|
|
265
|
+
reset)
|
|
266
|
+
for _arg in "${_sub_args[@]}"; do
|
|
267
|
+
if [ "$_arg" = "--hard" ] || [[ "$_arg" == --hard=* ]]; then
|
|
268
|
+
echo "BLOCKED: git reset --hard is disabled." >&2; return 1
|
|
269
|
+
fi
|
|
270
|
+
done; command git "$@"; return $? ;;
|
|
271
|
+
checkout)
|
|
272
|
+
for _arg in "${_sub_args[@]}"; do
|
|
273
|
+
if [ "$_arg" = "--" ]; then
|
|
274
|
+
echo "BLOCKED: git checkout -- is disabled." >&2; return 1
|
|
275
|
+
fi
|
|
276
|
+
case "$_arg" in -f*|--force*)
|
|
277
|
+
echo "BLOCKED: git checkout -f is disabled." >&2; return 1 ;;
|
|
278
|
+
esac
|
|
279
|
+
done; command git "$@"; return $? ;;
|
|
280
|
+
switch)
|
|
281
|
+
for _arg in "${_sub_args[@]}"; do
|
|
282
|
+
case "$_arg" in -f*|--force*)
|
|
283
|
+
echo "BLOCKED: git switch -f is disabled." >&2; return 1 ;;
|
|
284
|
+
esac
|
|
285
|
+
done; command git "$@"; return $? ;;
|
|
286
|
+
restore)
|
|
287
|
+
echo "BLOCKED: git restore is disabled." >&2; return 1 ;;
|
|
288
|
+
clean)
|
|
289
|
+
for _arg in "${_sub_args[@]}"; do
|
|
290
|
+
case "$_arg" in -f*|--force*)
|
|
291
|
+
echo "BLOCKED: git clean -f is disabled." >&2; return 1 ;;
|
|
292
|
+
esac
|
|
293
|
+
done; command git "$@"; return $? ;;
|
|
294
|
+
*) command git "$@"; return $? ;;
|
|
251
295
|
esac
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
checkout)
|
|
269
|
-
for _arg in "${_sub_args[@]}"; do
|
|
270
|
-
if [ "$_arg" = "--" ]; then
|
|
271
|
-
echo "BLOCKED: git checkout -- is disabled." >&2; return 1
|
|
272
|
-
fi
|
|
273
|
-
case "$_arg" in -f*|--force*)
|
|
274
|
-
echo "BLOCKED: git checkout -f is disabled." >&2; return 1 ;;
|
|
275
|
-
esac
|
|
276
|
-
done; command git "$@"; return $? ;;
|
|
277
|
-
switch)
|
|
278
|
-
for _arg in "${_sub_args[@]}"; do
|
|
279
|
-
case "$_arg" in -f*|--force*)
|
|
280
|
-
echo "BLOCKED: git switch -f is disabled." >&2; return 1 ;;
|
|
281
|
-
esac
|
|
282
|
-
done; command git "$@"; return $? ;;
|
|
283
|
-
restore)
|
|
284
|
-
echo "BLOCKED: git restore is disabled." >&2; return 1 ;;
|
|
285
|
-
clean)
|
|
286
|
-
for _arg in "${_sub_args[@]}"; do
|
|
287
|
-
case "$_arg" in -f*|--force*)
|
|
288
|
-
echo "BLOCKED: git clean -f is disabled." >&2; return 1 ;;
|
|
289
|
-
esac
|
|
290
|
-
done; command git "$@"; return $? ;;
|
|
291
|
-
*) command git "$@"; return $? ;;
|
|
296
|
+
}
|
|
297
|
+
- path: scripts/setup-git-guards.sh
|
|
298
|
+
content: |
|
|
299
|
+
#!/bin/bash
|
|
300
|
+
# setup-git-guards.sh — install shell function guards for destructive git ops.
|
|
301
|
+
# Auto-detects shell: zsh → ~/.zshenv, bash → ~/.bashrc (Git Bash on Windows).
|
|
302
|
+
set -euo pipefail
|
|
303
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
304
|
+
if [ -z "$repo_root" ]; then echo "ERROR: not inside a git repository" >&2; exit 1; fi
|
|
305
|
+
guard_script="$repo_root/scripts/git-guard.sh"
|
|
306
|
+
marker="# forge-git-guard"
|
|
307
|
+
# Auto-detect target profile file based on current shell
|
|
308
|
+
case "${SHELL:-}" in
|
|
309
|
+
*zsh*) profile_file="${HOME}/.zshenv" ;;
|
|
310
|
+
*bash*) profile_file="${HOME}/.bashrc" ;;
|
|
311
|
+
*) profile_file="${HOME}/.bashrc" ;;
|
|
292
312
|
esac
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
--
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
313
|
+
mode="${1:-install}"
|
|
314
|
+
case "$mode" in
|
|
315
|
+
install)
|
|
316
|
+
if [ ! -f "$guard_script" ]; then echo "ERROR: $guard_script not found" >&2; exit 1; fi
|
|
317
|
+
chmod +x "$guard_script" 2>/dev/null || true
|
|
318
|
+
if [ -f "$profile_file" ]; then
|
|
319
|
+
tmp="$(mktemp)"
|
|
320
|
+
awk -v marker="$marker" '$0 == marker { skip=2; next } skip > 0 { skip--; next } { print }' "$profile_file" > "$tmp" || true
|
|
321
|
+
mv "$tmp" "$profile_file" 2>/dev/null || true
|
|
322
|
+
fi
|
|
323
|
+
echo "" >> "$profile_file"
|
|
324
|
+
echo "$marker" >> "$profile_file"
|
|
325
|
+
echo "[ -f \"$guard_script\" ] && source \"$guard_script\"" >> "$profile_file"
|
|
326
|
+
echo "Git guards installed into $profile_file. Restart your shell or run: source $guard_script" ;;
|
|
327
|
+
--verify)
|
|
328
|
+
if [ -z "${FORGE_GIT_GUARD:-}" ]; then echo "MISSING: git guard not loaded" >&2; exit 1; fi
|
|
329
|
+
if ! grep -qF "$marker" "$profile_file" 2>/dev/null; then echo "MISSING: guard not in $profile_file" >&2; exit 1; fi
|
|
330
|
+
echo "OK: git guards installed and loaded" ;;
|
|
331
|
+
--remove)
|
|
332
|
+
if [ -f "$profile_file" ]; then
|
|
333
|
+
tmp="$(mktemp)"
|
|
334
|
+
awk -v marker="$marker" '$0 == marker { skip=2; next } skip > 0 { skip--; next } { print }' "$profile_file" > "$tmp" || true
|
|
335
|
+
mv "$tmp" "$profile_file" 2>/dev/null || true
|
|
336
|
+
fi
|
|
337
|
+
echo "Git guards removed from $profile_file" ;;
|
|
338
|
+
*) echo "Usage: bash scripts/setup-git-guards.sh [install|--verify|--remove]" >&2; exit 1 ;;
|
|
339
|
+
esac
|
|
340
|
+
- path: scripts/clean-stale-stashes.sh
|
|
341
|
+
content: |
|
|
342
|
+
#!/bin/bash
|
|
343
|
+
# clean-stale-stashes.sh — detect and optionally drop stale git stash entries.
|
|
344
|
+
# Usage: bash scripts/clean-stale-stashes.sh [--drop|--drop-all]
|
|
345
|
+
set -euo pipefail
|
|
346
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
347
|
+
if [ -z "$repo_root" ]; then echo "ERROR: not inside a git repository" >&2; exit 1; fi
|
|
348
|
+
mode="report"
|
|
349
|
+
if [ "${1:-}" = "--drop" ]; then mode="drop"; elif [ "${1:-}" = "--drop-all" ]; then mode="drop-all"; fi
|
|
350
|
+
stash_count="$(git -C "$repo_root" stash list 2>/dev/null | wc -l)"
|
|
351
|
+
if [ "$stash_count" -eq 0 ]; then exit 0; fi
|
|
352
|
+
stale_indices=() total="$stash_count"
|
|
353
|
+
for ((i = 0; i < total; i++)); do
|
|
354
|
+
ref="stash@{$i}"
|
|
355
|
+
if [ "$mode" = "drop-all" ]; then stale_indices+=("$i"); continue; fi
|
|
356
|
+
tracked_changes="$(git -C "$repo_root" stash show "$ref" --stat 2>/dev/null || true)"
|
|
357
|
+
untracked_files="$(git -C "$repo_root" stash show --include-untracked "$ref" --stat 2>/dev/null || true)"
|
|
358
|
+
if [ -z "$tracked_changes" ] && [ -z "$untracked_files" ]; then stale_indices+=("$i"); continue; fi
|
|
359
|
+
if [ -z "$tracked_changes" ] && [ -n "$untracked_files" ]; then
|
|
360
|
+
all_committed=true
|
|
361
|
+
while IFS= read -r filepath; do
|
|
362
|
+
[ -z "$filepath" ] && continue
|
|
363
|
+
if ! git -C "$repo_root" cat-file -e "HEAD:$filepath" 2>/dev/null; then all_committed=false; break; fi
|
|
364
|
+
done < <(git -C "$repo_root" stash show --include-untracked "$ref" --name-only 2>/dev/null || true)
|
|
365
|
+
if [ "$all_committed" = true ]; then stale_indices+=("$i"); fi
|
|
333
366
|
fi
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
set -euo pipefail
|
|
343
|
-
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
344
|
-
if [ -z "$repo_root" ]; then echo "ERROR: not inside a git repository" >&2; exit 1; fi
|
|
345
|
-
mode="report"
|
|
346
|
-
if [ "${1:-}" = "--drop" ]; then mode="drop"; elif [ "${1:-}" = "--drop-all" ]; then mode="drop-all"; fi
|
|
347
|
-
stash_count="$(git -C "$repo_root" stash list 2>/dev/null | wc -l)"
|
|
348
|
-
if [ "$stash_count" -eq 0 ]; then exit 0; fi
|
|
349
|
-
stale_indices=() total="$stash_count"
|
|
350
|
-
for ((i = 0; i < total; i++)); do
|
|
351
|
-
ref="stash@{$i}"
|
|
352
|
-
if [ "$mode" = "drop-all" ]; then stale_indices+=("$i"); continue; fi
|
|
353
|
-
tracked_changes="$(git -C "$repo_root" stash show "$ref" --stat 2>/dev/null || true)"
|
|
354
|
-
untracked_files="$(git -C "$repo_root" stash show --include-untracked "$ref" --stat 2>/dev/null || true)"
|
|
355
|
-
if [ -z "$tracked_changes" ] && [ -z "$untracked_files" ]; then stale_indices+=("$i"); continue; fi
|
|
356
|
-
if [ -z "$tracked_changes" ] && [ -n "$untracked_files" ]; then
|
|
357
|
-
all_committed=true
|
|
358
|
-
while IFS= read -r filepath; do
|
|
359
|
-
[ -z "$filepath" ] && continue
|
|
360
|
-
if ! git -C "$repo_root" cat-file -e "HEAD:$filepath" 2>/dev/null; then all_committed=false; break; fi
|
|
361
|
-
done < <(git -C "$repo_root" stash show --include-untracked "$ref" --name-only 2>/dev/null || true)
|
|
362
|
-
if [ "$all_committed" = true ]; then stale_indices+=("$i"); fi
|
|
367
|
+
done
|
|
368
|
+
if [ ${#stale_indices[@]} -eq 0 ]; then exit 0; fi
|
|
369
|
+
if [ "$mode" = "report" ]; then
|
|
370
|
+
echo "STALE STASH ENTRIES DETECTED (${#stale_indices[@]} of $stash_count):"
|
|
371
|
+
for idx in "${stale_indices[@]}"; do
|
|
372
|
+
echo " stash@{$idx}: $(git -C "$repo_root" stash list | sed -n "$((idx + 1))p")"
|
|
373
|
+
done
|
|
374
|
+
echo "Clean with: bash scripts/clean-stale-stashes.sh --drop"; exit 1
|
|
363
375
|
fi
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for idx in "${stale_indices[@]}"; do
|
|
369
|
-
echo " stash@{$idx}: $(git -C "$repo_root" stash list | sed -n "$((idx + 1))p")"
|
|
376
|
+
if [ "$mode" = "drop-all" ]; then git -C "$repo_root" stash clear; echo "Dropped all $stash_count entries."; exit 0; fi
|
|
377
|
+
for ((i = ${#stale_indices[@]} - 1; i >= 0; i--)); do
|
|
378
|
+
idx="${stale_indices[$i]}"
|
|
379
|
+
git -C "$repo_root" stash drop "stash@{$idx}" 2>/dev/null || true
|
|
370
380
|
done
|
|
371
|
-
echo "
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
set -euo pipefail
|
|
385
|
-
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
386
|
-
if [ -z "$repo_root" ]; then echo "ERROR: not inside a git repository" >&2; exit 1; fi
|
|
387
|
-
dirty=0 output=""
|
|
388
|
-
main_status="$(git -C "$repo_root" status --short 2>/dev/null || true)"
|
|
389
|
-
if [ -n "$main_status" ]; then
|
|
390
|
-
output+="DIRTY: $repo_root\n$(printf '%s\n' "$main_status" | sed 's/^/ /')\n"
|
|
391
|
-
dirty=1
|
|
392
|
-
fi
|
|
393
|
-
while IFS= read -r gitdir; do
|
|
394
|
-
nested_root="$(dirname "$gitdir")"
|
|
395
|
-
[ "$nested_root" = "$repo_root" ] && continue
|
|
396
|
-
nested_status="$(git -C "$nested_root" status --short 2>/dev/null || true)"
|
|
397
|
-
if [ -n "$nested_status" ]; then
|
|
398
|
-
output+="DIRTY: $nested_root\n$(printf '%s\n' "$nested_status" | sed 's/^/ /')\n"
|
|
381
|
+
echo "Dropped ${#stale_indices[@]} stale entry/entries."; exit 0
|
|
382
|
+
- path: scripts/check-clean-trees.sh
|
|
383
|
+
content: |
|
|
384
|
+
#!/bin/bash
|
|
385
|
+
# check-clean-trees.sh — verify all git trees are clean.
|
|
386
|
+
# Checks repo root + all nested .git directories up to 3 levels deep.
|
|
387
|
+
set -euo pipefail
|
|
388
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
389
|
+
if [ -z "$repo_root" ]; then echo "ERROR: not inside a git repository" >&2; exit 1; fi
|
|
390
|
+
dirty=0 output=""
|
|
391
|
+
main_status="$(git -C "$repo_root" status --short 2>/dev/null || true)"
|
|
392
|
+
if [ -n "$main_status" ]; then
|
|
393
|
+
output+="DIRTY: $repo_root\n$(printf '%s\n' "$main_status" | sed 's/^/ /')\n"
|
|
399
394
|
dirty=1
|
|
400
395
|
fi
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
396
|
+
while IFS= read -r gitdir; do
|
|
397
|
+
nested_root="$(dirname "$gitdir")"
|
|
398
|
+
[ "$nested_root" = "$repo_root" ] && continue
|
|
399
|
+
nested_status="$(git -C "$nested_root" status --short 2>/dev/null || true)"
|
|
400
|
+
if [ -n "$nested_status" ]; then
|
|
401
|
+
output+="DIRTY: $nested_root\n$(printf '%s\n' "$nested_status" | sed 's/^/ /')\n"
|
|
402
|
+
dirty=1
|
|
403
|
+
fi
|
|
404
|
+
done < <(find "$repo_root" -maxdepth 3 -name ".git" -type d 2>/dev/null | head -20)
|
|
405
|
+
if [ "$dirty" -eq 1 ]; then
|
|
406
|
+
echo -e "$output" | head -40
|
|
407
|
+
echo "---"
|
|
408
|
+
echo "Uncommitted changes detected. Commit before session end."
|
|
409
|
+
exit 1
|
|
410
|
+
fi
|
|
411
|
+
exit 0
|
|
412
|
+
- path: hooks/pre-user-prompt.sh
|
|
413
|
+
content: |
|
|
414
|
+
#!/bin/bash
|
|
415
|
+
# Pre-user-prompt hook: session-end protocol + stale-stash + git-guard checks.
|
|
416
|
+
# Requires jq for JSON payload parsing.
|
|
417
|
+
set -euo pipefail
|
|
418
|
+
payload="$(cat)"
|
|
419
|
+
user_prompt="$(printf '%s' "$payload" | jq -r '.tool_info.user_prompt // empty' 2>/dev/null || true)"
|
|
420
|
+
if [ -z "$user_prompt" ]; then exit 0; fi
|
|
421
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
|
|
422
|
+
if [ -n "$repo_root" ] && [ -x "$repo_root/scripts/clean-stale-stashes.sh" ]; then
|
|
423
|
+
stash_output="$("$repo_root/scripts/clean-stale-stashes.sh" 2>&1 || true)"
|
|
424
|
+
if [ -n "$stash_output" ]; then
|
|
425
|
+
echo "⚠️ STALE GIT STASH DETECTED" >&2
|
|
426
|
+
echo "$stash_output" >&2
|
|
427
|
+
echo "Clean with: bash scripts/clean-stale-stashes.sh --drop" >&2
|
|
428
|
+
echo "" >&2
|
|
429
|
+
fi
|
|
426
430
|
fi
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
431
|
+
if [ -n "$repo_root" ] && [ -f "$repo_root/scripts/git-guard.sh" ]; then
|
|
432
|
+
if [ -z "${FORGE_GIT_GUARD:-}" ]; then
|
|
433
|
+
echo "⚠️ GIT GUARD NOT LOADED — destructive git operations are NOT blocked." >&2
|
|
434
|
+
echo "Run: source $repo_root/scripts/git-guard.sh" >&2
|
|
435
|
+
echo "" >&2
|
|
436
|
+
fi
|
|
433
437
|
fi
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
bashAvailable = false;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (!bashAvailable) {
|
|
490
|
-
// Print one-time warning if not already sent
|
|
491
|
-
if (!existsSync(warningFlag)) {
|
|
492
|
-
console.error("⚠️ bash not found — pre-user-prompt hook is disabled.");
|
|
493
|
-
console.error("Install Git Bash (Git for Windows) or WSL to enable agent-safety hooks.");
|
|
494
|
-
console.error("");
|
|
495
|
-
try {
|
|
496
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
497
|
-
writeFileSync(warningFlag, new Date().toISOString(), "utf8");
|
|
498
|
-
} catch {
|
|
499
|
-
// Best-effort — don't crash if cache dir is not writable
|
|
500
|
-
}
|
|
438
|
+
session_end_phrases=("Завершаем эту сессию" "Завершаем сессию" "Заканчиваем сессию" "Завершить сессию" "End session" "Wrap up" "Session end" "/session-end")
|
|
439
|
+
matched=""
|
|
440
|
+
for phrase in "${session_end_phrases[@]}"; do
|
|
441
|
+
if printf '%s' "$user_prompt" | grep -qiF "$phrase"; then matched="$phrase"; break; fi
|
|
442
|
+
done
|
|
443
|
+
if [ -z "$matched" ]; then exit 0; fi
|
|
444
|
+
cat >&2 <<EOF
|
|
445
|
+
SESSION-END PROTOCOL TRIGGERED (matched: "$matched")
|
|
446
|
+
The user's original message was:
|
|
447
|
+
> $user_prompt
|
|
448
|
+
You MUST invoke the fo-session-retro skill via the skill tool BEFORE producing
|
|
449
|
+
any other output. This is a NON-NEGOTIABLE BLOCKED GATE per PREFERENCES.md.
|
|
450
|
+
DO NOT produce a closing summary or ad-hoc output. The closing block must come
|
|
451
|
+
from fo-session-retro's report.
|
|
452
|
+
Protocol steps:
|
|
453
|
+
1. Verify clean working trees — run: bash scripts/check-clean-trees.sh
|
|
454
|
+
2. Invoke fo-session-retro via the skill tool
|
|
455
|
+
3. The retro skill's report IS the session-end output
|
|
456
|
+
EOF
|
|
457
|
+
if [ -n "$repo_root" ] && [ -x "$repo_root/scripts/check-clean-trees.sh" ]; then
|
|
458
|
+
tree_output="$("$repo_root/scripts/check-clean-trees.sh" 2>&1 || true)"
|
|
459
|
+
if [ -n "$tree_output" ]; then echo "" >&2; echo "$tree_output" >&2; fi
|
|
460
|
+
fi
|
|
461
|
+
exit 2
|
|
462
|
+
- path: hooks/pre-user-prompt-wrapper.mjs
|
|
463
|
+
content: |
|
|
464
|
+
#!/usr/bin/env node
|
|
465
|
+
// pre-user-prompt-wrapper.mjs — cross-platform wrapper for pre-user-prompt.sh.
|
|
466
|
+
// Detects bash availability, prints one-time warning if bash not found,
|
|
467
|
+
// then delegates to bash hooks/pre-user-prompt.sh with stdin piped.
|
|
468
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
469
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
470
|
+
import { dirname, join } from "node:path";
|
|
471
|
+
import { fileURLToPath } from "node:url";
|
|
472
|
+
|
|
473
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
474
|
+
const scriptPath = join(__dirname, "pre-user-prompt.sh");
|
|
475
|
+
const cacheDir = join(__dirname, "..", ".cache");
|
|
476
|
+
const warningFlag = join(cacheDir, "git-guard-warning-sent");
|
|
477
|
+
|
|
478
|
+
// If pre-user-prompt.sh doesn't exist, silently exit
|
|
479
|
+
if (!existsSync(scriptPath)) {
|
|
480
|
+
process.exit(0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Check if bash is available
|
|
484
|
+
let bashAvailable = false;
|
|
485
|
+
try {
|
|
486
|
+
execFileSync("bash", ["--version"], { stdio: "ignore" });
|
|
487
|
+
bashAvailable = true;
|
|
488
|
+
} catch {
|
|
489
|
+
bashAvailable = false;
|
|
501
490
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
"pre_user_prompt": [
|
|
515
|
-
{
|
|
516
|
-
"command": "node $ROOT_WORKSPACE_PATH/hooks/pre-user-prompt-wrapper.mjs",
|
|
517
|
-
"show_output": true
|
|
491
|
+
|
|
492
|
+
if (!bashAvailable) {
|
|
493
|
+
// Print one-time warning if not already sent
|
|
494
|
+
if (!existsSync(warningFlag)) {
|
|
495
|
+
console.error("⚠️ bash not found — pre-user-prompt hook is disabled.");
|
|
496
|
+
console.error("Install Git Bash (Git for Windows) or WSL to enable agent-safety hooks.");
|
|
497
|
+
console.error("");
|
|
498
|
+
try {
|
|
499
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
500
|
+
writeFileSync(warningFlag, new Date().toISOString(), "utf8");
|
|
501
|
+
} catch {
|
|
502
|
+
// Best-effort — don't crash if cache dir is not writable
|
|
518
503
|
}
|
|
519
|
-
|
|
504
|
+
}
|
|
505
|
+
process.exit(0);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Delegate to bash script with stdin piped
|
|
509
|
+
const child = spawn("bash", [scriptPath], { stdio: ["inherit", "inherit", "inherit"] });
|
|
510
|
+
child.on("exit", (code) => {
|
|
511
|
+
process.exit(code ?? 0);
|
|
512
|
+
});
|
|
513
|
+
- path: .windsurf/hooks.json
|
|
514
|
+
content: |
|
|
515
|
+
{
|
|
516
|
+
"hooks": {
|
|
517
|
+
"pre_user_prompt": [
|
|
518
|
+
{
|
|
519
|
+
"command": "node $ROOT_WORKSPACE_PATH/hooks/pre-user-prompt-wrapper.mjs",
|
|
520
|
+
"show_output": true
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
}
|
|
520
524
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
# __PROJECT_NAME__
|
|
525
|
+
- path: missions/.gitkeep
|
|
526
|
+
content: ""
|
|
527
|
+
- path: .forge/pinned.yaml
|
|
528
|
+
content: |
|
|
529
|
+
# Pinned foundation files (DNA-62, RFC-0733)
|
|
530
|
+
# Protect mode: warns on delete/move. Freeze mode: blocks modify too.
|
|
531
|
+
entries:
|
|
532
|
+
- path: tools/kernel.config.ts
|
|
533
|
+
mode: protect
|
|
534
|
+
- path: forge.yaml
|
|
535
|
+
mode: protect
|
|
536
|
+
- path: pnpm-workspace.yaml
|
|
537
|
+
mode: protect
|
|
538
|
+
- path: scripts/git-guard.sh
|
|
539
|
+
mode: protect
|
|
540
|
+
- path: scripts/setup-git-guards.sh
|
|
541
|
+
mode: protect
|
|
542
|
+
- path: hooks/pre-user-prompt.sh
|
|
543
|
+
mode: protect
|
|
544
|
+
- path: hooks/pre-user-prompt-wrapper.mjs
|
|
545
|
+
mode: protect
|
|
546
|
+
- path: README.md
|
|
547
|
+
content: |
|
|
548
|
+
# __PROJECT_NAME__
|
|
546
549
|
|
|
547
|
-
|
|
550
|
+
A Werkstatt site workshop — full-lifecycle site management powered by the Warpgogol platform.
|
|
548
551
|
|
|
549
|
-
|
|
552
|
+
## Prerequisites
|
|
550
553
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
- Node.js 24+
|
|
555
|
+
- pnpm 10+
|
|
556
|
+
- jq (for pre-user-prompt hook)
|
|
557
|
+
- An npm read token for the private `@warpgogol` scope (issued at workshop provisioning)
|
|
558
|
+
- On Windows: Git Bash or WSL (for shell scripts)
|
|
556
559
|
|
|
557
|
-
|
|
560
|
+
## Setup
|
|
558
561
|
|
|
559
|
-
|
|
562
|
+
### 1. Configure the npm token
|
|
560
563
|
|
|
561
|
-
|
|
564
|
+
This workshop depends on private @warpgogol packages. Either export the token:
|
|
562
565
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
```sh
|
|
567
|
+
export NPM_TOKEN=your_token_here
|
|
568
|
+
```
|
|
566
569
|
|
|
567
|
-
|
|
570
|
+
or edit `.npmrc` and replace `YOUR_NPM_TOKEN` with the token.
|
|
568
571
|
|
|
569
|
-
|
|
572
|
+
### 2. Install dependencies
|
|
570
573
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
+
```sh
|
|
575
|
+
pnpm install
|
|
576
|
+
```
|
|
574
577
|
|
|
575
|
-
|
|
578
|
+
### 3. Bootstrap workshop identity (required before first site)
|
|
576
579
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
+
```sh
|
|
581
|
+
pnpm exec werkstatt run identity.bootstrap --operator-name <name> --domain <domain>
|
|
582
|
+
```
|
|
580
583
|
|
|
581
|
-
|
|
584
|
+
This creates `werkstatt.identity.json` and prints a private key — store it as `SIGNING_PRIVATE_KEY` in your environment. `sternsystem.register` refuses to run until both exist.
|
|
582
585
|
|
|
583
|
-
|
|
586
|
+
### 4. Register your first site
|
|
584
587
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
+
```sh
|
|
589
|
+
pnpm exec werkstatt run sternsystem.register --id <site-id> --cosmicStar <StarName> --mirrors <cache,bare,...>
|
|
590
|
+
```
|
|
588
591
|
|
|
589
|
-
|
|
592
|
+
### 5. Verify the workshop
|
|
590
593
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
594
|
+
```sh
|
|
595
|
+
pnpm exec forge run doctor
|
|
596
|
+
pnpm exec werkstatt run sternsystem.validate --id <site-id>
|
|
597
|
+
```
|
|
595
598
|
|
|
596
|
-
|
|
599
|
+
## Stack
|
|
597
600
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
+
- Engine: @warpgogol/werkstatt-engine + @warpgogol/werkstatt-site
|
|
602
|
+
- Governance: @warpgogol/forge
|
|
603
|
+
- Stack profile: site-workshop
|
|
601
604
|
|
|
602
|
-
|
|
605
|
+
## Platform updates
|
|
603
606
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
+
```sh
|
|
608
|
+
pnpm exec werkstatt run werkstatt.platform.update --to latest
|
|
609
|
+
```
|
|
607
610
|
|
|
608
|
-
|
|
611
|
+
Bumps `@warpgogol/*` dependencies and reports Sternsystems whose `pinnedPlatform` drifts.
|
|
609
612
|
nextSteps: |
|
|
610
613
|
# Next Steps
|
|
611
614
|
|
|
@@ -651,4 +654,4 @@ nextSteps: |
|
|
|
651
654
|
|
|
652
655
|
Per-client granular npm tokens are issued at workshop provisioning and revoked at exit/handover-out. To rotate: replace the token in `.npmrc` or update `NPM_TOKEN`, then re-run `pnpm exec forge run doctor` to verify.
|
|
653
656
|
install:
|
|
654
|
-
- pnpm add -wD @warpgogol/forge @warpgogol/werkstatt-engine @warpgogol/werkstatt-site prettier vitest
|
|
657
|
+
- pnpm add -wD @warpgogol/forge @warpgogol/werkstatt-engine @warpgogol/werkstatt-site prettier vitest
|