claude-dev-env 8.28.0 → 8.29.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/.agents/skills/codex-cleanse/SKILL.md +36 -0
- package/.agents/skills/codex-cleanse/config/constants.mjs +11 -0
- package/.agents/skills/codex-cleanse/scripts/cleanse.mjs +458 -0
- package/.agents/skills/codex-cleanse/scripts/cleanse.test.mjs +708 -0
- package/bin/codex-skill-load-block.mjs +50 -2
- package/bin/codex-skill-load-block.test.mjs +120 -1
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install.mjs +28 -27
- package/bin/install.question-presentation.test.mjs +264 -0
- package/bin/install.test.mjs +13 -7
- package/bin/seed-codex-pstack-models.mjs +13 -2
- package/bin/seed-codex-pstack-models.test.mjs +45 -1
- package/package.json +1 -1
- package/rules/question-presentation.md +12 -0
- package/rules/verify-before-asking.md +3 -3
package/bin/install.test.mjs
CHANGED
|
@@ -56,6 +56,7 @@ import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
|
|
|
56
56
|
import {
|
|
57
57
|
SKILL_LOAD_BLOCK_START,
|
|
58
58
|
SKILL_LOAD_INSTRUCTION,
|
|
59
|
+
QUESTION_PRESENTATION_BLOCK_START,
|
|
59
60
|
withSkillLoadBlock,
|
|
60
61
|
} from './codex-skill-load-block.mjs';
|
|
61
62
|
|
|
@@ -3094,10 +3095,9 @@ test('a repeat install preserves Codex pstack configuration', t => {
|
|
|
3094
3095
|
|
|
3095
3096
|
runPstackInstaller(sandbox.homeDirectory, [], sandbox.environment);
|
|
3096
3097
|
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
);
|
|
3098
|
+
const codexGuidance = readFileSync(join(codexHome, 'AGENTS.md'), 'utf8');
|
|
3099
|
+
assert.ok(codexGuidance.startsWith(withSkillLoadBlock('Custom Codex guidance\n')));
|
|
3100
|
+
assert.ok(codexGuidance.includes(QUESTION_PRESENTATION_BLOCK_START));
|
|
3101
3101
|
assert.equal(readFileSync(join(codexHome, 'pstack-models.md'), 'utf8'), 'session hook: off\n');
|
|
3102
3102
|
});
|
|
3103
3103
|
|
|
@@ -3140,10 +3140,13 @@ test('an absent host command is reported and leaves the other host installed', t
|
|
|
3140
3140
|
[],
|
|
3141
3141
|
);
|
|
3142
3142
|
assert.equal(existsSync(join(sandbox.homeDirectory, '.codex', 'pstack-models.md')), false);
|
|
3143
|
-
assert.
|
|
3143
|
+
assert.match(
|
|
3144
|
+
readFileSync(join(sandbox.homeDirectory, '.codex', 'AGENTS.md'), 'utf8'),
|
|
3145
|
+
/<!-- claude-dev-env question presentation: start -->/,
|
|
3146
|
+
);
|
|
3144
3147
|
});
|
|
3145
3148
|
|
|
3146
|
-
test('--no-pstack
|
|
3149
|
+
test('--no-pstack skips the plugin and delivers Codex question guidance', t => {
|
|
3147
3150
|
const sandbox = pstackPluginSandbox(t);
|
|
3148
3151
|
|
|
3149
3152
|
const installerOutput = runPstackInstaller(
|
|
@@ -3153,7 +3156,10 @@ test('--no-pstack leaves both hosts untouched', t => {
|
|
|
3153
3156
|
assert.deepEqual(sandbox.recordedCommands(), []);
|
|
3154
3157
|
assert.doesNotMatch(installerOutput, /Pstack \(/);
|
|
3155
3158
|
assert.equal(existsSync(join(sandbox.homeDirectory, '.codex', 'pstack-models.md')), false);
|
|
3156
|
-
assert.
|
|
3159
|
+
assert.match(
|
|
3160
|
+
readFileSync(join(sandbox.homeDirectory, '.codex', 'AGENTS.md'), 'utf8'),
|
|
3161
|
+
/<!-- claude-dev-env question presentation: start -->/,
|
|
3162
|
+
);
|
|
3157
3163
|
});
|
|
3158
3164
|
|
|
3159
3165
|
function continuityCommandCount(configurationPath) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, lstatSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, constants as filesystemConstants } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { hasOnlyQuestionPresentationBlock } from './codex-skill-load-block.mjs';
|
|
4
5
|
|
|
5
6
|
const CODEX_PRESET_PATH = fileURLToPath(new URL('../pstack-codex-models.md', import.meta.url));
|
|
6
7
|
|
|
@@ -11,14 +12,24 @@ function pathExists(path) {
|
|
|
11
12
|
export function seedCodexPstackModels(codexHome, writeAgentGuidance = writeFileSync) {
|
|
12
13
|
const sheetPath = join(codexHome, 'pstack-models.md');
|
|
13
14
|
const agentsPath = join(codexHome, 'AGENTS.md');
|
|
14
|
-
if (pathExists(sheetPath)
|
|
15
|
+
if (pathExists(sheetPath)) return null;
|
|
16
|
+
const agentsEntry = lstatSync(agentsPath, { throwIfNoEntry: false });
|
|
17
|
+
if (agentsEntry && !agentsEntry.isFile()) return null;
|
|
18
|
+
const existingGuidance = agentsEntry ? readFileSync(agentsPath, 'utf8') : '';
|
|
19
|
+
if (agentsEntry && !hasOnlyQuestionPresentationBlock(existingGuidance)) return null;
|
|
15
20
|
|
|
16
21
|
const preset = readFileSync(CODEX_PRESET_PATH, 'utf8');
|
|
17
22
|
const agentGuidance = preset.replace(/^session hook: on\n?$/m, '');
|
|
23
|
+
const separator = agentGuidance.endsWith('\n') ? '\n' : '\n\n';
|
|
24
|
+
const combinedGuidance = existingGuidance
|
|
25
|
+
? `${agentGuidance}${separator}${existingGuidance}`
|
|
26
|
+
: agentGuidance;
|
|
18
27
|
mkdirSync(codexHome, { recursive: true });
|
|
19
28
|
copyFileSync(CODEX_PRESET_PATH, sheetPath, filesystemConstants.COPYFILE_EXCL);
|
|
20
29
|
try {
|
|
21
|
-
writeAgentGuidance(agentsPath,
|
|
30
|
+
writeAgentGuidance(agentsPath, combinedGuidance, {
|
|
31
|
+
encoding: 'utf8', flag: agentsEntry ? 'w' : 'wx',
|
|
32
|
+
});
|
|
22
33
|
} catch (writeError) {
|
|
23
34
|
unlinkSync(sheetPath);
|
|
24
35
|
throw writeError;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import { strict as assert } from 'node:assert';
|
|
3
|
-
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, lstatSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { seedCodexPstackModels } from './seed-codex-pstack-models.mjs';
|
|
7
|
+
import { QUESTION_PRESENTATION_BLOCK_END, QUESTION_PRESENTATION_BLOCK_START } from './codex-skill-load-block.mjs';
|
|
8
|
+
|
|
9
|
+
const QUESTION_GUIDANCE = `${QUESTION_PRESENTATION_BLOCK_START}\nAsk one question.\n${QUESTION_PRESENTATION_BLOCK_END}\n`;
|
|
7
10
|
|
|
8
11
|
test('fresh Codex home gets the selected roles and hook setting', (context) => {
|
|
9
12
|
const root = mkdtempSync(join(tmpdir(), 'cde-pstack-models-'));
|
|
@@ -70,3 +73,44 @@ for (const existingFile of ['AGENTS.md', 'pstack-models.md']) {
|
|
|
70
73
|
assert.equal(existsSync(join(codexHome, existingFile === 'AGENTS.md' ? 'pstack-models.md' : 'AGENTS.md')), false);
|
|
71
74
|
});
|
|
72
75
|
}
|
|
76
|
+
|
|
77
|
+
test('a question-only Codex file receives model defaults and keeps its block', (context) => {
|
|
78
|
+
const root = mkdtempSync(join(tmpdir(), 'cde-pstack-models-'));
|
|
79
|
+
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
80
|
+
const codexHome = join(root, '.codex');
|
|
81
|
+
mkdirSync(codexHome);
|
|
82
|
+
writeFileSync(join(codexHome, 'AGENTS.md'), QUESTION_GUIDANCE);
|
|
83
|
+
|
|
84
|
+
const paths = seedCodexPstackModels(codexHome);
|
|
85
|
+
|
|
86
|
+
assert.deepEqual(paths, [join(codexHome, 'pstack-models.md'), join(codexHome, 'AGENTS.md')]);
|
|
87
|
+
assert.match(readFileSync(join(codexHome, 'pstack-models.md'), 'utf8'), /^session hook: on$/m);
|
|
88
|
+
const agents = readFileSync(join(codexHome, 'AGENTS.md'), 'utf8');
|
|
89
|
+
assert.match(agents, /^feature, refactoring: gpt-6-sol$/m);
|
|
90
|
+
assert.ok(agents.endsWith(QUESTION_GUIDANCE));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('a question block with personal notes does not trigger model seeding', (context) => {
|
|
94
|
+
const root = mkdtempSync(join(tmpdir(), 'cde-pstack-models-'));
|
|
95
|
+
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
96
|
+
const codexHome = join(root, '.codex');
|
|
97
|
+
mkdirSync(codexHome);
|
|
98
|
+
writeFileSync(join(codexHome, 'AGENTS.md'), `${QUESTION_GUIDANCE}My notes\n`);
|
|
99
|
+
|
|
100
|
+
assert.equal(seedCodexPstackModels(codexHome), null);
|
|
101
|
+
assert.equal(existsSync(join(codexHome, 'pstack-models.md')), false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('a linked question-only Codex file does not trigger model seeding', (context) => {
|
|
105
|
+
const root = mkdtempSync(join(tmpdir(), 'cde-pstack-models-'));
|
|
106
|
+
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
107
|
+
const codexHome = join(root, '.codex');
|
|
108
|
+
const linkedGuidancePath = join(root, 'linked-guidance.md');
|
|
109
|
+
mkdirSync(codexHome);
|
|
110
|
+
writeFileSync(linkedGuidancePath, QUESTION_GUIDANCE);
|
|
111
|
+
symlinkSync(linkedGuidancePath, join(codexHome, 'AGENTS.md'));
|
|
112
|
+
|
|
113
|
+
assert.equal(seedCodexPstackModels(codexHome), null);
|
|
114
|
+
assert.equal(lstatSync(join(codexHome, 'AGENTS.md')).isSymbolicLink(), true);
|
|
115
|
+
assert.equal(readFileSync(linkedGuidancePath, 'utf8'), QUESTION_GUIDANCE);
|
|
116
|
+
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Present questions clearly
|
|
2
|
+
|
|
3
|
+
Before asking for input, give a short formatted brief in chat. Put the context and the effect of each choice there. Add a visual or artifact only when it helps the decision.
|
|
4
|
+
|
|
5
|
+
For Codex, when a visual comparison helps, discover callable tools for Page creation, visualization upload, and Page opening. Create the comparison, upload its visualization, and open its Page in the current task before asking. If a tool is unavailable, creation, upload, or opening fails, or opening queues, put the comparison in chat before asking. Inline Visualize references appear only in final replies. Use the user's submitted answer for the decision. An async dispatch acknowledgement or saved visual supplies context only. Wait for the answer before work that depends on it.
|
|
6
|
+
|
|
7
|
+
Ask one short, self-contained question with two or three clear choices. Use short labels, and keep each supported choice description to one short sentence. Use the same labels in the chat brief, comparison, and picker.
|
|
8
|
+
|
|
9
|
+
Discover the current session's native question tool and its schema. Claude may expose `AskUserQuestion`.
|
|
10
|
+
Codex may expose `request_user_input_async` or `request_user_input`. Follow the tool's mode restrictions.
|
|
11
|
+
Put the question in its declared title or question field and choices in its declared string or object fields.
|
|
12
|
+
When no native tool is usable, ask the short question and choices in chat.
|
|
@@ -8,7 +8,7 @@ If a question can be answered by inspecting files, running a command, querying a
|
|
|
8
8
|
|
|
9
9
|
## Decision Checklist
|
|
10
10
|
|
|
11
|
-
Before
|
|
11
|
+
Before using the current session's native question tool or asking a clarifying question in chat, evaluate:
|
|
12
12
|
|
|
13
13
|
| Check | Action |
|
|
14
14
|
|---|---|
|
|
@@ -21,14 +21,14 @@ Before writing any AskUserQuestion or asking a clarifying question in chat, eval
|
|
|
21
21
|
| Is the answer retrievable from any available MCP tool? | Use the tool. |
|
|
22
22
|
| Did the user already state a criterion, standard, or line that decides this? | Apply it, state the call and the reason, and keep going. |
|
|
23
23
|
|
|
24
|
-
Only after confirming the answer cannot be obtained through any available tool, ask the user.
|
|
24
|
+
Only after confirming the answer cannot be obtained through any available tool, ask the user. Present the question using [Present questions clearly](question-presentation.md).
|
|
25
25
|
|
|
26
26
|
## Prior-session facts expire
|
|
27
27
|
|
|
28
28
|
A path, port, branch name, or config value you recall from an earlier session counts as unanswered until a tool re-checks it this session. Memory records what was true when it was written; the file may have moved, the port may be down, the branch may have merged. Treat every recalled fact as a claim to re-ground, not an answer to reuse.
|
|
29
29
|
|
|
30
30
|
- When a tool can settle it, re-check in silence and act on the fresh result — no question to the user.
|
|
31
|
-
- When no tool can settle it and the user has a stake in the answer,
|
|
31
|
+
- When no tool can settle it and the user has a stake in the answer, use the current session's native question tool when available.
|
|
32
32
|
|
|
33
33
|
## Questions That Belong to the User
|
|
34
34
|
|