pinokiod 7.3.14 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kernel/api/index.js +111 -15
- package/kernel/api/script/index.js +10 -0
- package/kernel/autolaunch.js +111 -0
- package/kernel/environment.js +89 -1
- package/kernel/git.js +9 -19
- package/kernel/index.js +142 -43
- package/kernel/launch_requirements.js +1115 -0
- package/kernel/ready.js +231 -0
- package/kernel/script.js +16 -0
- package/kernel/shells.js +9 -1
- package/kernel/workspace_status.js +111 -45
- package/package.json +1 -1
- package/server/autolaunch.js +725 -0
- package/server/index.js +244 -411
- package/server/public/logs.js +15 -1
- package/server/public/style.css +99 -31
- package/server/views/app.ejs +263 -159
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- package/server/views/logs.ejs +14 -12
- package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
- package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
- package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
- package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
- package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
- package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
- package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
- package/server/views/partials/home_action_modal.ejs +4 -1
- package/server/views/partials/launch_requirements_status_client.ejs +271 -0
- package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
- package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
- package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
- package/server/views/terminal.ejs +196 -2
- package/test/home-autolaunch-live-ui.test.js +455 -0
- package/test/launch-requirements-browser.test.js +579 -0
- package/test/launch-requirements-contract-coverage.test.js +627 -0
- package/test/launch-requirements-real-browser.js +625 -0
- package/test/launch-requirements-status-client.test.js +132 -0
- package/test/launch-requirements.test.js +1806 -0
- package/test/launch-settings-ui.test.js +370 -0
- package/test/ready-state.test.js +49 -0
- package/test/server-autolaunch.test.js +1052 -0
- package/test/startup-git-index-benchmark.js +409 -0
- package/test/startup-git-index-browser.js +320 -0
- package/test/startup-git-index-performance.test.js +380 -0
- package/test/startup-git-index-refactor.test.js +450 -0
- package/test/startup-git-index-route.test.js +588 -0
- package/test/universal-launcher.smoke.spec.js +10 -9
- package/test/workspace-gitignore-benchmark.js +815 -0
- package/test/workspace-gitignore-path-scoped.test.js +256 -0
- package/spec/INSTRUCTION_SYNC.md +0 -432
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const { execFileSync } = require('node:child_process')
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const os = require('node:os')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const test = require('node:test')
|
|
7
|
+
|
|
8
|
+
const WorkspaceStatusManager = require('../kernel/workspace_status')
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
buildCurrentWorkspaceIgnoreEngine,
|
|
12
|
+
candidateIgnoredByPathScopedGitignores,
|
|
13
|
+
currentEngineIgnored,
|
|
14
|
+
normalizePath,
|
|
15
|
+
} = require('./workspace-gitignore-benchmark')
|
|
16
|
+
|
|
17
|
+
function run(cmd, args, cwd) {
|
|
18
|
+
return execFileSync(cmd, args, {
|
|
19
|
+
cwd,
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
GIT_AUTHOR_NAME: 'Codex Test',
|
|
24
|
+
GIT_AUTHOR_EMAIL: 'codex-test@example.com',
|
|
25
|
+
GIT_COMMITTER_NAME: 'Codex Test',
|
|
26
|
+
GIT_COMMITTER_EMAIL: 'codex-test@example.com',
|
|
27
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
28
|
+
},
|
|
29
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
30
|
+
}).trim()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function git(args, cwd) {
|
|
34
|
+
return run('git', args, cwd)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeTempRoot(prefix) {
|
|
38
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function initRepo(repoPath) {
|
|
42
|
+
fs.mkdirSync(repoPath, { recursive: true })
|
|
43
|
+
git(['init'], repoPath)
|
|
44
|
+
git(['config', 'user.name', 'Codex Test'], repoPath)
|
|
45
|
+
git(['config', 'user.email', 'codex-test@example.com'], repoPath)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeFile(filePath, content = 'x\n') {
|
|
49
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
50
|
+
fs.writeFileSync(filePath, content)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeRecord(workspaceRoot, repoRoot, repoRelative) {
|
|
54
|
+
const absolutePath = path.resolve(repoRoot, repoRelative)
|
|
55
|
+
return {
|
|
56
|
+
key: `${repoRoot}\0${normalizePath(repoRelative)}`,
|
|
57
|
+
repoRoot,
|
|
58
|
+
repoRelative: normalizePath(repoRelative),
|
|
59
|
+
absolutePath,
|
|
60
|
+
workspaceRelative: normalizePath(path.relative(workspaceRoot, absolutePath)),
|
|
61
|
+
statusCode: '??',
|
|
62
|
+
rawLine: `?? ${normalizePath(repoRelative)}`,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
test('path-scoped candidate honors top-level workspace ignores for deep nested repos', async () => {
|
|
67
|
+
const root = makeTempRoot('pinokio-gitignore-top-')
|
|
68
|
+
try {
|
|
69
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
70
|
+
const nested = path.join(workspace, 'a', 'b', 'c', 'repo')
|
|
71
|
+
initRepo(workspace)
|
|
72
|
+
writeFile(path.join(workspace, '.gitignore'), 'a/b/c/repo/parent-hidden.txt\n')
|
|
73
|
+
initRepo(nested)
|
|
74
|
+
writeFile(path.join(nested, 'parent-hidden.txt'))
|
|
75
|
+
writeFile(path.join(nested, 'visible.txt'))
|
|
76
|
+
|
|
77
|
+
const records = [
|
|
78
|
+
makeRecord(workspace, nested, 'parent-hidden.txt'),
|
|
79
|
+
makeRecord(workspace, nested, 'visible.txt'),
|
|
80
|
+
]
|
|
81
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
82
|
+
|
|
83
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
84
|
+
assert.equal(candidate.ignored.has(records[1].key), false)
|
|
85
|
+
} finally {
|
|
86
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('path-scoped candidate includes intermediate .gitignore files before the leaf repo', async () => {
|
|
91
|
+
const root = makeTempRoot('pinokio-gitignore-intermediate-')
|
|
92
|
+
try {
|
|
93
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
94
|
+
const middle = path.join(workspace, 'a')
|
|
95
|
+
const leaf = path.join(middle, 'b', 'c', 'repo')
|
|
96
|
+
initRepo(workspace)
|
|
97
|
+
initRepo(middle)
|
|
98
|
+
writeFile(path.join(middle, '.gitignore'), 'b/c/repo/intermediate-hidden.txt\n')
|
|
99
|
+
initRepo(leaf)
|
|
100
|
+
writeFile(path.join(leaf, '.gitignore'), 'leaf-hidden.txt\n')
|
|
101
|
+
writeFile(path.join(leaf, 'intermediate-hidden.txt'))
|
|
102
|
+
writeFile(path.join(leaf, 'leaf-hidden.txt'))
|
|
103
|
+
writeFile(path.join(leaf, 'visible.txt'))
|
|
104
|
+
|
|
105
|
+
const records = [
|
|
106
|
+
makeRecord(workspace, leaf, 'intermediate-hidden.txt'),
|
|
107
|
+
makeRecord(workspace, leaf, 'leaf-hidden.txt'),
|
|
108
|
+
makeRecord(workspace, leaf, 'visible.txt'),
|
|
109
|
+
]
|
|
110
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
111
|
+
|
|
112
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
113
|
+
assert.equal(candidate.ignored.has(records[1].key), true)
|
|
114
|
+
assert.equal(candidate.ignored.has(records[2].key), false)
|
|
115
|
+
} finally {
|
|
116
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('path-scoped candidate hides tracked paths the same way current JS filtering does', async () => {
|
|
121
|
+
const root = makeTempRoot('pinokio-gitignore-no-index-')
|
|
122
|
+
try {
|
|
123
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
124
|
+
initRepo(workspace)
|
|
125
|
+
writeFile(path.join(workspace, 'tracked-hidden.txt'), 'tracked\n')
|
|
126
|
+
git(['add', 'tracked-hidden.txt'], workspace)
|
|
127
|
+
git(['commit', '-m', 'track hidden fixture'], workspace)
|
|
128
|
+
writeFile(path.join(workspace, '.gitignore'), 'tracked-hidden.txt\n')
|
|
129
|
+
|
|
130
|
+
const records = [makeRecord(workspace, workspace, 'tracked-hidden.txt')]
|
|
131
|
+
const current = currentEngineIgnored((await buildCurrentWorkspaceIgnoreEngine(workspace)).engine, records)
|
|
132
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
133
|
+
|
|
134
|
+
assert.equal(current.ignored.has(records[0].key), true)
|
|
135
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
136
|
+
} finally {
|
|
137
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('path-scoped candidate evaluates symlinked directory paths as strings', async () => {
|
|
142
|
+
const root = makeTempRoot('pinokio-gitignore-symlink-')
|
|
143
|
+
try {
|
|
144
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
145
|
+
const external = path.join(root, 'external-models')
|
|
146
|
+
initRepo(workspace)
|
|
147
|
+
writeFile(path.join(workspace, '.gitignore'), '/models/\n/output/\n')
|
|
148
|
+
fs.mkdirSync(path.join(workspace, 'models'), { recursive: true })
|
|
149
|
+
fs.mkdirSync(path.join(external, 'output'), { recursive: true })
|
|
150
|
+
fs.mkdirSync(path.join(external, 'checkpoints'), { recursive: true })
|
|
151
|
+
fs.symlinkSync(path.join(external, 'checkpoints'), path.join(workspace, 'models', 'checkpoints'), 'dir')
|
|
152
|
+
fs.symlinkSync(path.join(external, 'output'), path.join(workspace, 'output'), 'dir')
|
|
153
|
+
|
|
154
|
+
const records = [
|
|
155
|
+
makeRecord(workspace, workspace, 'models/checkpoints/deleted-placeholder'),
|
|
156
|
+
makeRecord(workspace, workspace, 'output/deleted-placeholder'),
|
|
157
|
+
]
|
|
158
|
+
const current = currentEngineIgnored((await buildCurrentWorkspaceIgnoreEngine(workspace)).engine, records)
|
|
159
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
160
|
+
|
|
161
|
+
assert.equal(current.ignored.has(records[0].key), true)
|
|
162
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
163
|
+
assert.equal(current.ignored.has(records[1].key), true)
|
|
164
|
+
assert.equal(candidate.ignored.has(records[1].key), true)
|
|
165
|
+
assert.equal(candidate.metrics.checked, records.length)
|
|
166
|
+
} finally {
|
|
167
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('candidate filtering matches current workspace pre-scan model on multi-level fixture', async () => {
|
|
172
|
+
const root = makeTempRoot('pinokio-gitignore-parity-')
|
|
173
|
+
try {
|
|
174
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
175
|
+
const middle = path.join(workspace, 'packages', 'middle')
|
|
176
|
+
const leaf = path.join(middle, 'runtime', 'leaf')
|
|
177
|
+
initRepo(workspace)
|
|
178
|
+
writeFile(path.join(workspace, '.gitignore'), 'workspace-hidden.txt\npackages/middle/runtime/leaf/workspace-leaf-hidden.txt\n')
|
|
179
|
+
writeFile(path.join(workspace, 'packages', '.gitignore'), 'middle/runtime/leaf/package-hidden.txt\n')
|
|
180
|
+
initRepo(middle)
|
|
181
|
+
writeFile(path.join(middle, '.gitignore'), 'runtime/leaf/middle-hidden.txt\n')
|
|
182
|
+
initRepo(leaf)
|
|
183
|
+
writeFile(path.join(leaf, '.gitignore'), 'leaf-hidden.txt\n')
|
|
184
|
+
|
|
185
|
+
const records = [
|
|
186
|
+
makeRecord(workspace, workspace, 'workspace-hidden.txt'),
|
|
187
|
+
makeRecord(workspace, leaf, 'package-hidden.txt'),
|
|
188
|
+
makeRecord(workspace, leaf, 'workspace-leaf-hidden.txt'),
|
|
189
|
+
makeRecord(workspace, leaf, 'middle-hidden.txt'),
|
|
190
|
+
makeRecord(workspace, leaf, 'leaf-hidden.txt'),
|
|
191
|
+
makeRecord(workspace, leaf, 'visible.txt'),
|
|
192
|
+
]
|
|
193
|
+
for (const record of records) {
|
|
194
|
+
writeFile(record.absolutePath)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const current = currentEngineIgnored((await buildCurrentWorkspaceIgnoreEngine(workspace)).engine, records)
|
|
198
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
199
|
+
const manager = new WorkspaceStatusManager({ enableWatchers: false })
|
|
200
|
+
const runtimeIgnored = await manager.filterPathScopedGitIgnored(workspace, records)
|
|
201
|
+
|
|
202
|
+
assert.deepEqual(new Set(candidate.ignored), new Set(current.ignored))
|
|
203
|
+
assert.deepEqual(new Set(runtimeIgnored), new Set(current.ignored))
|
|
204
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
205
|
+
assert.equal(candidate.ignored.has(records[1].key), true)
|
|
206
|
+
assert.equal(candidate.ignored.has(records[2].key), true)
|
|
207
|
+
assert.equal(candidate.ignored.has(records[3].key), true)
|
|
208
|
+
assert.equal(candidate.ignored.has(records[4].key), true)
|
|
209
|
+
assert.equal(candidate.ignored.has(records[5].key), false)
|
|
210
|
+
} finally {
|
|
211
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('path-scoped candidate preserves current leading-slash normalization behavior', async () => {
|
|
216
|
+
const root = makeTempRoot('pinokio-gitignore-leading-slash-')
|
|
217
|
+
try {
|
|
218
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
219
|
+
initRepo(workspace)
|
|
220
|
+
writeFile(path.join(workspace, '.gitignore'), '/AGENTS.md\n')
|
|
221
|
+
writeFile(path.join(workspace, 'app', 'AGENTS.md'))
|
|
222
|
+
|
|
223
|
+
const records = [makeRecord(workspace, workspace, 'app/AGENTS.md')]
|
|
224
|
+
const current = currentEngineIgnored((await buildCurrentWorkspaceIgnoreEngine(workspace)).engine, records)
|
|
225
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
226
|
+
|
|
227
|
+
assert.equal(current.ignored.has(records[0].key), true)
|
|
228
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
229
|
+
} finally {
|
|
230
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('path-scoped candidate preserves current trailing directory path behavior', async () => {
|
|
235
|
+
const root = makeTempRoot('pinokio-gitignore-trailing-dir-')
|
|
236
|
+
try {
|
|
237
|
+
const workspace = path.join(root, 'api', 'foo')
|
|
238
|
+
initRepo(workspace)
|
|
239
|
+
writeFile(path.join(workspace, 'app', '.gitignore'), [
|
|
240
|
+
'demosite/timelines/*',
|
|
241
|
+
'!demosite/timelines/friendfeed.com/',
|
|
242
|
+
'!demosite/timelines/friendfeed.com/**',
|
|
243
|
+
'',
|
|
244
|
+
].join('\n'))
|
|
245
|
+
fs.mkdirSync(path.join(workspace, 'app', 'demosite', 'timelines', 'friendfeed.com'), { recursive: true })
|
|
246
|
+
|
|
247
|
+
const records = [makeRecord(workspace, workspace, 'app/demosite/timelines/friendfeed.com')]
|
|
248
|
+
const current = currentEngineIgnored((await buildCurrentWorkspaceIgnoreEngine(workspace)).engine, records)
|
|
249
|
+
const candidate = await candidateIgnoredByPathScopedGitignores(workspace, records)
|
|
250
|
+
|
|
251
|
+
assert.equal(current.ignored.has(records[0].key), true)
|
|
252
|
+
assert.equal(candidate.ignored.has(records[0].key), true)
|
|
253
|
+
} finally {
|
|
254
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
255
|
+
}
|
|
256
|
+
})
|
package/spec/INSTRUCTION_SYNC.md
DELETED
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
# Pinokio Instruction Sync
|
|
2
|
-
|
|
3
|
-
This document defines the intended behavior of Pinokio instruction sync, including generated instruction files, mirrored outputs, and the managed sources that feed them.
|
|
4
|
-
|
|
5
|
-
Unless explicitly marked non-normative, statements in this document describe behavior that future implementations should preserve.
|
|
6
|
-
|
|
7
|
-
## Purpose
|
|
8
|
-
|
|
9
|
-
Fix three related problems with minimal behavioral change:
|
|
10
|
-
|
|
11
|
-
1. Pinokio updates currently wipe the entire `PINOKIO_HOME/plugin` and `PINOKIO_HOME/prototype` trees, including user-installed content.
|
|
12
|
-
2. Generated instruction files such as `PINOKIO_HOME/AGENTS.md` go stale after first creation.
|
|
13
|
-
3. Git-backed installs can become half-initialized (`.git` exists but working files are missing), which breaks runtime flows such as app creation.
|
|
14
|
-
|
|
15
|
-
## Sync Inputs and Outputs
|
|
16
|
-
|
|
17
|
-
### 1. Managed instruction sources
|
|
18
|
-
|
|
19
|
-
Paths:
|
|
20
|
-
|
|
21
|
-
- `PINOKIO_HOME/plugin/code`
|
|
22
|
-
- `PINOKIO_HOME/prototype/system`
|
|
23
|
-
- `PINOKIO_HOME/network/system`
|
|
24
|
-
|
|
25
|
-
Population:
|
|
26
|
-
|
|
27
|
-
- cloned by Pinokio from upstream Git repositories
|
|
28
|
-
|
|
29
|
-
Policy:
|
|
30
|
-
|
|
31
|
-
- On Pinokio version change, refresh these managed repos only.
|
|
32
|
-
- On normal startup, bootstrap only if missing.
|
|
33
|
-
- At runtime, if a needed file inside one of these repos is missing, attempt a targeted Git restore for that path only.
|
|
34
|
-
|
|
35
|
-
### 2. Managed downloaded instruction sources
|
|
36
|
-
|
|
37
|
-
Paths:
|
|
38
|
-
|
|
39
|
-
- `PINOKIO_HOME/prototype/PINOKIO.md`
|
|
40
|
-
- `PINOKIO_HOME/prototype/PTERM.md`
|
|
41
|
-
|
|
42
|
-
Population:
|
|
43
|
-
|
|
44
|
-
- downloaded by Pinokio
|
|
45
|
-
|
|
46
|
-
Policy:
|
|
47
|
-
|
|
48
|
-
- On Pinokio version change, refresh these two files only.
|
|
49
|
-
- On normal startup, download only if missing.
|
|
50
|
-
|
|
51
|
-
### 3. Generated instruction outputs
|
|
52
|
-
|
|
53
|
-
Paths:
|
|
54
|
-
|
|
55
|
-
- `PINOKIO_HOME/AGENTS.md`
|
|
56
|
-
- `PINOKIO_HOME/CLAUDE.md`
|
|
57
|
-
- `PINOKIO_HOME/GEMINI.md`
|
|
58
|
-
- `PINOKIO_HOME/QWEN.md`
|
|
59
|
-
- `PINOKIO_HOME/.windsurfrules`
|
|
60
|
-
- `PINOKIO_HOME/.cursorrules`
|
|
61
|
-
- `PINOKIO_HOME/.clinerules`
|
|
62
|
-
- `PINOKIO_HOME/.geminiignore`
|
|
63
|
-
|
|
64
|
-
Population:
|
|
65
|
-
|
|
66
|
-
- generated by `Environment.init(...)` from `PINOKIO_HOME/prototype/system/AGENTS.md`
|
|
67
|
-
|
|
68
|
-
User customization input:
|
|
69
|
-
|
|
70
|
-
- `PINOKIO_HOME/SOUL.md`
|
|
71
|
-
|
|
72
|
-
Policy:
|
|
73
|
-
|
|
74
|
-
- `SOUL.md` is the persistent user-editable customization file.
|
|
75
|
-
- The output files above are generated artifacts, not user-owned source files.
|
|
76
|
-
- In v1, this policy applies to the home root only, not app roots.
|
|
77
|
-
- Per-app generated `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` / `QWEN.md` / rule files remain on the current behavior unless explicitly redesigned later.
|
|
78
|
-
- On every home environment initialization, Pinokio must:
|
|
79
|
-
- ensure `PINOKIO_HOME/SOUL.md` exists; create it as an empty file if missing
|
|
80
|
-
- render the base instruction template
|
|
81
|
-
- append a fixed wrapper section that says the following `SOUL.md` content overrides earlier general Pinokio defaults on conflict
|
|
82
|
-
- append the contents of `SOUL.md`
|
|
83
|
-
- write each generated output only if its content differs from the desired content
|
|
84
|
-
- `.geminiignore` remains a write-if-changed generated file.
|
|
85
|
-
|
|
86
|
-
### 4. Managed skills and mirrored instruction outputs
|
|
87
|
-
|
|
88
|
-
Paths:
|
|
89
|
-
|
|
90
|
-
- `PINOKIO_HOME/skills/index.json`
|
|
91
|
-
- `PINOKIO_HOME/skills/gepeto/SKILL.md`
|
|
92
|
-
- `PINOKIO_HOME/skills/pinokio/SKILL.md`
|
|
93
|
-
- `~/.agents/skills/gepeto/SKILL.md`
|
|
94
|
-
- `~/.claude/skills/gepeto/SKILL.md`
|
|
95
|
-
- `~/.hermes/skills/gepeto/SKILL.md`
|
|
96
|
-
- `~/.agents/skills/pinokio/SKILL.md`
|
|
97
|
-
- `~/.claude/skills/pinokio/SKILL.md`
|
|
98
|
-
- `~/.hermes/skills/pinokio/SKILL.md`
|
|
99
|
-
|
|
100
|
-
Population:
|
|
101
|
-
|
|
102
|
-
- `PINOKIO_HOME/skills/gepeto/SKILL.md` is generated from the current effective `PINOKIO_HOME/AGENTS.md`
|
|
103
|
-
- `PINOKIO_HOME/skills/pinokio/SKILL.md` is generated from the source-controlled file in this repo at `prototype/system/SKILL_PINOKIO.md`
|
|
104
|
-
- enabled managed skills are published into external agent roots during managed skill sync
|
|
105
|
-
|
|
106
|
-
Policy:
|
|
107
|
-
|
|
108
|
-
- `PINOKIO_HOME/skills/index.json` is the managed skill source of truth.
|
|
109
|
-
- Built-in skills `pinokio` and `gepeto` are present by default and may be disabled but not removed.
|
|
110
|
-
- Downloaded skills are cloned into `PINOKIO_HOME/skills/<id>` and publish as `pinokio-<id>` by default.
|
|
111
|
-
- Enabled managed skills are published to `~/.agents/skills`, `~/.claude/skills`, and `~/.hermes/skills`.
|
|
112
|
-
- Published copies include a `.pinokio-managed.json` marker.
|
|
113
|
-
- Pinokio may update or remove published copies only when they are marked Pinokio-owned, or when migrating a legacy copy whose `SKILL.md` content matches the generated desired content.
|
|
114
|
-
- If a publish target already contains a non-Pinokio skill, sync must report a conflict and not overwrite it.
|
|
115
|
-
- If a published copy is manually deleted while the skill remains enabled, sync treats that as drift and recreates it.
|
|
116
|
-
|
|
117
|
-
### 5. Out-of-scope user installs
|
|
118
|
-
|
|
119
|
-
Paths:
|
|
120
|
-
|
|
121
|
-
- everything under `PINOKIO_HOME/plugin/*` except `plugin/code`
|
|
122
|
-
- everything under `PINOKIO_HOME/prototype/*` except `prototype/system`, `prototype/PINOKIO.md`, and `prototype/PTERM.md`
|
|
123
|
-
- everything under `PINOKIO_HOME/network/*` except `network/system`
|
|
124
|
-
|
|
125
|
-
Population:
|
|
126
|
-
|
|
127
|
-
- installed or created by the user
|
|
128
|
-
|
|
129
|
-
Policy:
|
|
130
|
-
|
|
131
|
-
- Never delete or refresh these automatically on Pinokio version change.
|
|
132
|
-
- Never bootstrap over them on startup.
|
|
133
|
-
- Only consider a targeted missing-path Git repair if Pinokio actually tries to use a missing file inside one of these repos.
|
|
134
|
-
|
|
135
|
-
## Sync Triggers
|
|
136
|
-
|
|
137
|
-
### A. Pinokio version change
|
|
138
|
-
|
|
139
|
-
Required behavior:
|
|
140
|
-
|
|
141
|
-
- refresh `plugin/code`
|
|
142
|
-
- refresh `prototype/system`
|
|
143
|
-
- refresh `network/system`
|
|
144
|
-
- refresh `prototype/PINOKIO.md`
|
|
145
|
-
- refresh `prototype/PTERM.md`
|
|
146
|
-
|
|
147
|
-
Prohibited behavior:
|
|
148
|
-
|
|
149
|
-
- delete `PINOKIO_HOME/plugin`
|
|
150
|
-
- delete `PINOKIO_HOME/prototype`
|
|
151
|
-
- touch custom siblings under those roots
|
|
152
|
-
|
|
153
|
-
Compatibility note:
|
|
154
|
-
|
|
155
|
-
- Keep the current "delete then let init recreate" model, but narrow it to the specific Pinokio-owned repos and docs above.
|
|
156
|
-
- Do not mark the stored home version as updated until the managed refresh has completed successfully, otherwise a failed refresh can leave the home in a broken state that no longer retries on next startup.
|
|
157
|
-
|
|
158
|
-
### B. Normal startup
|
|
159
|
-
|
|
160
|
-
Required behavior:
|
|
161
|
-
|
|
162
|
-
- bootstrap managed repos/docs only if missing
|
|
163
|
-
- run home environment initialization only after the managed repos/docs needed by that initialization are present
|
|
164
|
-
- regenerate generated instruction outputs from `template + SOUL.md`
|
|
165
|
-
- resync mirror outputs if their source content changed
|
|
166
|
-
|
|
167
|
-
Prohibited behavior:
|
|
168
|
-
|
|
169
|
-
- refresh user-owned installs
|
|
170
|
-
- wipe managed repos/docs unless this is a version-change path
|
|
171
|
-
|
|
172
|
-
Ordering requirement:
|
|
173
|
-
|
|
174
|
-
- After a version-change cleanup, Pinokio must not rely on the current startup order where `Environment.init({}, kernel)` can run before the async reclone/redownload of managed repos/docs completes.
|
|
175
|
-
- Home output regeneration must either:
|
|
176
|
-
- run after `plugin/code`, `prototype/system`, `network/system`, `PINOKIO.md`, and `PTERM.md` have been restored
|
|
177
|
-
- or be rerun once those managed inputs are restored
|
|
178
|
-
|
|
179
|
-
### C. Runtime missing-file repair
|
|
180
|
-
|
|
181
|
-
When Pinokio is about to fail because a requested file is missing:
|
|
182
|
-
|
|
183
|
-
1. Find the nearest existing parent directory of the missing path.
|
|
184
|
-
2. Walk upward until a Git repo root is found.
|
|
185
|
-
3. Verify the requested path is inside that repo.
|
|
186
|
-
4. Choose a usable source ref for restore.
|
|
187
|
-
5. Attempt to restore only the missing path from the repo using Git, not a repo-wide reset.
|
|
188
|
-
6. Retry the original operation once.
|
|
189
|
-
|
|
190
|
-
Rules:
|
|
191
|
-
|
|
192
|
-
- never hard reset the whole repo
|
|
193
|
-
- never delete the whole repo as part of runtime repair
|
|
194
|
-
- restore only the missing path or subtree
|
|
195
|
-
- do not assume `HEAD` is usable; the restore ref selection must handle orphan or otherwise broken `HEAD` states
|
|
196
|
-
- if repair fails, continue with the existing error path
|
|
197
|
-
|
|
198
|
-
Minimum restore-ref strategy:
|
|
199
|
-
|
|
200
|
-
1. If the current `HEAD` resolves to a commit and contains the requested path, restore from `HEAD`.
|
|
201
|
-
2. Otherwise, if a remote-tracking ref is available, fetch and restore from that ref.
|
|
202
|
-
3. Otherwise, fail without destructive repair.
|
|
203
|
-
|
|
204
|
-
## Instruction Composition
|
|
205
|
-
|
|
206
|
-
Generated instruction outputs are built from:
|
|
207
|
-
|
|
208
|
-
1. rendered base template from `prototype/system/AGENTS.md`
|
|
209
|
-
2. a fixed Pinokio-generated wrapper such as:
|
|
210
|
-
|
|
211
|
-
```md
|
|
212
|
-
## User Soul
|
|
213
|
-
|
|
214
|
-
The following instructions come from `SOUL.md`.
|
|
215
|
-
|
|
216
|
-
If this section conflicts with the general Pinokio defaults earlier in this document, follow this section.
|
|
217
|
-
|
|
218
|
-
If there is no conflict, follow both.
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
3. the contents of `PINOKIO_HOME/SOUL.md`
|
|
222
|
-
|
|
223
|
-
Notes:
|
|
224
|
-
|
|
225
|
-
- `SOUL.md` itself has no special vendor semantics; Pinokio is responsible for folding it into the real files that tools already read.
|
|
226
|
-
- `SOUL.md` should be appended after the base template so the user override layer appears last and has clearer precedence.
|
|
227
|
-
|
|
228
|
-
## Instruction Sync Migration
|
|
229
|
-
|
|
230
|
-
Old behavior:
|
|
231
|
-
|
|
232
|
-
- users may have edited `PINOKIO_HOME/AGENTS.md` directly because it used to be a persistent file
|
|
233
|
-
|
|
234
|
-
New behavior:
|
|
235
|
-
|
|
236
|
-
- `AGENTS.md` and its sibling outputs become generated files
|
|
237
|
-
- `SOUL.md` becomes the persistent customization input
|
|
238
|
-
|
|
239
|
-
Minimal migration:
|
|
240
|
-
|
|
241
|
-
- on first rollout, if `SOUL.md` does not exist and an existing generated output appears user-modified, preserve the old file as a backup rather than silently discarding it
|
|
242
|
-
- do not attempt automatic semantic merging of old `AGENTS.md` edits into `SOUL.md`
|
|
243
|
-
- this migration check must run before creating an empty `SOUL.md`, otherwise the "SOUL does not exist" condition becomes useless
|
|
244
|
-
|
|
245
|
-
## Non-Normative Implementation Notes
|
|
246
|
-
|
|
247
|
-
The following sections are retained as historical rollout notes from the initial implementation. They are informative only and do not override the behavioral requirements above.
|
|
248
|
-
|
|
249
|
-
## Appendix A. Initial Code Touch Points
|
|
250
|
-
|
|
251
|
-
### Version refresh scope
|
|
252
|
-
|
|
253
|
-
- `server/index.js`
|
|
254
|
-
- narrow the current version-mismatch cleanup logic so it refreshes only the managed repos/docs listed in this spec
|
|
255
|
-
|
|
256
|
-
### Managed repo/doc bootstrap
|
|
257
|
-
|
|
258
|
-
- `kernel/plugin.js`
|
|
259
|
-
- `kernel/prototype.js`
|
|
260
|
-
- keep existing bootstrap behavior; rely on the narrower cleanup above
|
|
261
|
-
|
|
262
|
-
### Generated outputs and `SOUL.md`
|
|
263
|
-
|
|
264
|
-
- `kernel/environment.js`
|
|
265
|
-
- change generation of `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` / `QWEN.md` / rules from "write if missing" to "ensure `SOUL.md`, compute desired content, write if changed"
|
|
266
|
-
- keep mirror sync in this file
|
|
267
|
-
|
|
268
|
-
### Runtime targeted Git repair
|
|
269
|
-
|
|
270
|
-
- `kernel/git.js`
|
|
271
|
-
- add helper(s) to discover a repo root for a path and restore a missing file/subtree only
|
|
272
|
-
|
|
273
|
-
- `server/index.js`
|
|
274
|
-
- before failing direct file serving for a missing path, attempt targeted repair once
|
|
275
|
-
|
|
276
|
-
- `kernel/index.js`
|
|
277
|
-
- before failing a missing module load, attempt targeted repair once and retry load
|
|
278
|
-
|
|
279
|
-
## Non-Goals
|
|
280
|
-
|
|
281
|
-
- No broad asset-management framework in v1.
|
|
282
|
-
- No UI is required for `SOUL.md`.
|
|
283
|
-
- No attempt to auto-refresh arbitrary user-installed plugins/prototypes on Pinokio version change.
|
|
284
|
-
- No repo-wide `git reset --hard` or equivalent destructive repair flow.
|
|
285
|
-
|
|
286
|
-
## Appendix B. Initial Implementation Constraints
|
|
287
|
-
|
|
288
|
-
- Keep code changes as small and localized as possible.
|
|
289
|
-
- Prefer editing existing lifecycle code in place over introducing new architectural layers.
|
|
290
|
-
- Do not add a general asset registry/framework in v1.
|
|
291
|
-
- Keep responsibilities where they already live:
|
|
292
|
-
- version-refresh scope and sequencing in `server/index.js`
|
|
293
|
-
- generated output composition in `kernel/environment.js`
|
|
294
|
-
- targeted Git repair helpers in `kernel/git.js`
|
|
295
|
-
- retry hooks only at the existing failure points in `server/index.js` and `kernel/index.js`
|
|
296
|
-
- Add a new helper file only if it materially reduces complexity at the touched call sites.
|
|
297
|
-
- Preserve existing behavior for healthy installs unless this spec explicitly changes it.
|
|
298
|
-
- Do not expand the v1 `SOUL.md` behavior beyond home-root generated outputs.
|
|
299
|
-
|
|
300
|
-
## Appendix C. Initial Rollout Checklist
|
|
301
|
-
|
|
302
|
-
### 1. Narrow managed refresh scope
|
|
303
|
-
|
|
304
|
-
File:
|
|
305
|
-
|
|
306
|
-
- `server/index.js`
|
|
307
|
-
|
|
308
|
-
Changes:
|
|
309
|
-
|
|
310
|
-
- replace whole-folder refresh of `PINOKIO_HOME/plugin` with refresh of `PINOKIO_HOME/plugin/code` only
|
|
311
|
-
- replace whole-folder refresh of `PINOKIO_HOME/prototype` with refresh of:
|
|
312
|
-
- `PINOKIO_HOME/prototype/system`
|
|
313
|
-
- `PINOKIO_HOME/prototype/PINOKIO.md`
|
|
314
|
-
- `PINOKIO_HOME/prototype/PTERM.md`
|
|
315
|
-
- keep refresh of `PINOKIO_HOME/network/system`
|
|
316
|
-
- do not touch custom sibling installs under `plugin/`, `prototype/`, or `network/`
|
|
317
|
-
|
|
318
|
-
### 2. Fix update sequencing
|
|
319
|
-
|
|
320
|
-
File:
|
|
321
|
-
|
|
322
|
-
- `server/index.js`
|
|
323
|
-
|
|
324
|
-
Changes:
|
|
325
|
-
|
|
326
|
-
- do not mark the stored Pinokio home version as updated until managed refresh has actually succeeded
|
|
327
|
-
- do not rely on the current order where home `Environment.init({}, kernel)` may run before managed repos/docs are restored
|
|
328
|
-
- ensure home output generation runs only after managed inputs are present, or rerun it after those inputs are restored
|
|
329
|
-
|
|
330
|
-
### 3. Add home-root `SOUL.md` generation
|
|
331
|
-
|
|
332
|
-
File:
|
|
333
|
-
|
|
334
|
-
- `kernel/environment.js`
|
|
335
|
-
|
|
336
|
-
Changes:
|
|
337
|
-
|
|
338
|
-
- scope this change to the home root only
|
|
339
|
-
- ensure `PINOKIO_HOME/SOUL.md` exists; create it empty if missing
|
|
340
|
-
- read the base template from `PINOKIO_HOME/prototype/system/AGENTS.md`
|
|
341
|
-
- compose final output as:
|
|
342
|
-
- rendered base template
|
|
343
|
-
- fixed override wrapper
|
|
344
|
-
- `SOUL.md` content
|
|
345
|
-
- change home-root output writing from "write if missing" to "write if changed"
|
|
346
|
-
- keep `.geminiignore` on its current write-if-changed behavior
|
|
347
|
-
|
|
348
|
-
### 4. Preserve current app-root behavior
|
|
349
|
-
|
|
350
|
-
File:
|
|
351
|
-
|
|
352
|
-
- `kernel/environment.js`
|
|
353
|
-
|
|
354
|
-
Changes:
|
|
355
|
-
|
|
356
|
-
- do not apply `SOUL.md` logic to app-root generated files in v1
|
|
357
|
-
- leave app-root `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` / `QWEN.md` / rule generation on current behavior unless required by a concrete bug
|
|
358
|
-
|
|
359
|
-
### 5. Move mirror sync behind managed skills
|
|
360
|
-
|
|
361
|
-
File:
|
|
362
|
-
|
|
363
|
-
- `kernel/environment.js`
|
|
364
|
-
|
|
365
|
-
Changes:
|
|
366
|
-
|
|
367
|
-
- generate built-in managed skill sources under `PINOKIO_HOME/skills`
|
|
368
|
-
- publish only enabled managed skills to external agent roots
|
|
369
|
-
- remove disabled Pinokio-owned published copies
|
|
370
|
-
- preserve user-owned publish target conflicts without overwriting
|
|
371
|
-
|
|
372
|
-
### 6. Add targeted missing-path Git repair helpers
|
|
373
|
-
|
|
374
|
-
File:
|
|
375
|
-
|
|
376
|
-
- `kernel/git.js`
|
|
377
|
-
|
|
378
|
-
Changes:
|
|
379
|
-
|
|
380
|
-
- add a helper to find the owning repo root for a missing target path
|
|
381
|
-
- add a helper to restore only the missing path or subtree
|
|
382
|
-
- do not hard reset or delete the repo
|
|
383
|
-
- do not assume `HEAD` is usable
|
|
384
|
-
- support a fallback restore ref strategy when `HEAD` is missing, orphaned, or otherwise unusable
|
|
385
|
-
|
|
386
|
-
### 7. Hook repair into direct file serving
|
|
387
|
-
|
|
388
|
-
File:
|
|
389
|
-
|
|
390
|
-
- `server/index.js`
|
|
391
|
-
|
|
392
|
-
Changes:
|
|
393
|
-
|
|
394
|
-
- before failing on a missing direct file path, attempt targeted repair once
|
|
395
|
-
- retry the original file stat/load once after repair
|
|
396
|
-
- if repair fails, preserve the current error path
|
|
397
|
-
|
|
398
|
-
### 8. Hook repair into module loading
|
|
399
|
-
|
|
400
|
-
File:
|
|
401
|
-
|
|
402
|
-
- `kernel/index.js`
|
|
403
|
-
|
|
404
|
-
Changes:
|
|
405
|
-
|
|
406
|
-
- before returning a failed module load caused by a missing path, attempt targeted repair once
|
|
407
|
-
- retry the original module load once after repair
|
|
408
|
-
- if repair fails, preserve the current error path
|
|
409
|
-
|
|
410
|
-
### 9. Add one-time migration protection for old edited home outputs
|
|
411
|
-
|
|
412
|
-
File:
|
|
413
|
-
|
|
414
|
-
- `kernel/environment.js`
|
|
415
|
-
|
|
416
|
-
Changes:
|
|
417
|
-
|
|
418
|
-
- before first rollout regeneration, detect whether an existing home `AGENTS.md` looks user-modified
|
|
419
|
-
- if so, preserve it as a backup instead of silently replacing it
|
|
420
|
-
- do not attempt automatic semantic merge into `SOUL.md`
|
|
421
|
-
- run this check before auto-creating `SOUL.md`
|
|
422
|
-
|
|
423
|
-
### 10. Verify the changed paths only
|
|
424
|
-
|
|
425
|
-
Acceptance criteria:
|
|
426
|
-
|
|
427
|
-
- version update no longer deletes custom installs under `plugin/*`, `prototype/*`, or `network/*`
|
|
428
|
-
- home-root `AGENTS.md` family updates when the base template changes
|
|
429
|
-
- home-root customization survives via `SOUL.md`
|
|
430
|
-
- `pinokio` skill still comes from repo-local `prototype/system/SKILL_PINOKIO.md`
|
|
431
|
-
- missing-file recovery works for a git-backed repo with a missing working-tree path
|
|
432
|
-
- healthy installs behave the same as before
|