claude-dev-env 7.7.1 → 8.0.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/agents/clean-coder.md +5 -6
- package/.agents/agents/issue-tracker.md +1 -1
- package/.agents/agents/pr-description-writer.md +1 -1
- package/.agents/agents/skill-writer-agent.md +1 -12
- package/.agents/agents/test_agent_frontmatter.py +2 -19
- package/.agents/skills/issue-tracker/SKILL.md +1 -1
- package/.agents/skills/orchestrator/SKILL.md +4 -5
- package/.agents/skills/pull-request/SKILL.md +8 -7
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +2 -3
- package/_shared/advisor/advisor-protocol.md +0 -1
- package/_shared/advisor/reference/lifecycle.md +0 -1
- package/_shared/advisor/reference/warm-up.md +1 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +3 -3
- package/bin/install-constants.mjs +0 -5
- package/bin/install-pstack-plugin.mjs +192 -0
- package/bin/install-pstack-plugin.test.mjs +181 -0
- package/bin/install.agents-home.test.mjs +0 -4
- package/bin/install.codex-rules.test.mjs +6 -0
- package/bin/install.cursor-rules.test.mjs +3 -253
- package/bin/install.mjs +55 -160
- package/bin/install.test.mjs +110 -149
- package/bin/prune-retired-pstack-store.mjs +83 -0
- package/bin/prune-retired-pstack-store.test.mjs +105 -0
- package/bin/prune-session-continuity.mjs +57 -0
- package/bin/prune-session-continuity.test.mjs +70 -0
- package/bin/resolve-install-root.mjs +2 -0
- package/bin/resolve-install-root.test.mjs +69 -0
- package/docs/CODE_RULES.md +1 -1
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/codex-compatibility.md +4 -16
- package/docs/nas-ssh-invocation.md +1 -1
- package/docs/references/prose-style-enforcement.md +2 -2
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
- package/hooks/blocking/code_rules_constants_config.py +1 -108
- package/hooks/blocking/code_rules_enforcer.py +9 -5
- package/hooks/blocking/code_rules_test_layout.py +6 -7
- package/hooks/blocking/test_code_rules_constants_config.py +13 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
- package/hooks/blocking/test_context_control_policy_files.py +0 -11
- package/hooks/hooks_constants/blocking_check_limits.py +1 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
- package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
- package/hooks/session/test_untracked_repo_detector.py +1 -1
- package/hooks/validators/code_quality_checks.py +15 -2
- package/hooks/validators/test_code_quality_checks.py +22 -0
- package/package.json +1 -2
- package/rules/ci-owns-the-gate.md +58 -0
- package/rules/code-standards.md +1 -2
- package/rules/docstring-prose-matches-implementation.md +2 -2
- package/rules/failure-blast-radius.md +1 -1
- package/rules/git-workflow.md +23 -11
- package/rules/orphan-css-class.md +1 -1
- package/rules/paired-test-coverage.md +1 -1
- package/rules/retired-hook-prose.md +1 -1
- package/rules/verify-before-asking.md +1 -1
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workflow-substitution-slots.md +1 -1
- package/scripts/Sync-RepoMain.ps1 +71 -6
- package/scripts/policy_lint/adapter_detectors.py +116 -10
- package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
- package/scripts/policy_lint/config/constants.py +3 -0
- package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
- package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
- package/.agents/skills/session-continuity/AGENTS.md +0 -1
- package/.agents/skills/session-continuity/CLAUDE.md +0 -1
- package/.agents/skills/session-continuity/README.md +0 -223
- package/.agents/skills/session-continuity/SKILL.md +0 -125
- package/.agents/skills/session-continuity/continuity.mjs +0 -445
- package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
- package/bin/install-session-continuity.mjs +0 -131
- package/bin/install-session-continuity.test.mjs +0 -106
- package/bin/install.pstack-plugin.test.mjs +0 -115
- package/bin/pstack-codex-discovery.test.mjs +0 -79
- package/bin/pstack-model-preferences.claude.json +0 -23
- package/bin/pstack-model-preferences.codex.json +0 -24
- package/bin/pstack.mjs +0 -392
- package/bin/pstack.test.mjs +0 -347
- package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
- package/bin/select-pstack-models.test.mjs +0 -776
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
- package/rules/agent-spawn-protocol.md +0 -9
- package/rules/ask-user-question-required.md +0 -31
- package/rules/claims-as-quotes.md +0 -55
- package/rules/confirm-implementation-forks.md +0 -11
- package/rules/file-global-constants.md +0 -81
- package/rules/hedging-claims.md +0 -7
- package/rules/measurement-denominators.md +0 -39
- package/rules/prompt-workflow-context-controls.md +0 -19
- package/rules/pstack-host-mapping.md +0 -48
- package/rules/pstack-models.md +0 -142
- package/rules/vault-context.md +0 -5
- package/scripts/pstack-adapters/common.md +0 -17
- package/scripts/pstack-adapters/create-skill.md +0 -14
- package/scripts/pstack-adapters/host-claude.md +0 -7
- package/scripts/pstack-adapters/host-codex.md +0 -11
- package/scripts/pstack-adapters/host-cursor.md +0 -7
- package/scripts/pstack.lock.json +0 -11
- package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
- package/scripts/select_pstack_models.mjs +0 -469
- /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
package/bin/install.mjs
CHANGED
|
@@ -35,24 +35,20 @@ import {
|
|
|
35
35
|
CODEX_RULES_PACKAGE_DIRECTORY_NAME,
|
|
36
36
|
CURSOR_SYNC_SCRIPT_FILE_NAME,
|
|
37
37
|
CURSOR_RULES_DIRECTORY_NAME,
|
|
38
|
-
PSTACK_MODEL_RULE_FILE_NAME,
|
|
39
38
|
SUBAGENT_MODEL_POLICY_FILE_NAME,
|
|
40
|
-
PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES,
|
|
41
39
|
WINDOWS_PYTHON_LAUNCHER_COMMAND,
|
|
42
40
|
PYTHON_PROBE_TIMEOUT_MILLISECONDS,
|
|
43
41
|
CODEX_NATIVE_ROUTING_MATCHER,
|
|
44
42
|
} from './install-constants.mjs';
|
|
45
43
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} from '../scripts/refresh_pstack_plugin_skills.mjs';
|
|
50
|
-
import { installPstack } from './pstack.mjs';
|
|
44
|
+
installPstackPlugin,
|
|
45
|
+
shouldInstallPstackPlugin,
|
|
46
|
+
} from './install-pstack-plugin.mjs';
|
|
51
47
|
import {
|
|
52
|
-
configureContinuityHosts,
|
|
53
48
|
continuityHostConfigurationPaths,
|
|
54
49
|
removeContinuityHooks,
|
|
55
|
-
} from './
|
|
50
|
+
} from './prune-session-continuity.mjs';
|
|
51
|
+
import { removeRetiredPstackInstallation } from './prune-retired-pstack-store.mjs';
|
|
56
52
|
import {
|
|
57
53
|
resolveInstallRoot,
|
|
58
54
|
parseExplicitTargetFromArgv,
|
|
@@ -432,90 +428,22 @@ export function runCursorRuleSync(pythonCommand, scriptPath, claudeRoot, cursorR
|
|
|
432
428
|
);
|
|
433
429
|
}
|
|
434
430
|
|
|
435
|
-
const PSTACK_RELEASE_OPT_OUT_FLAG = '--no-pstack';
|
|
436
|
-
const PSTACK_RELEASE_OPT_OUT_VARIABLE = 'CDE_INSTALL_PSTACK';
|
|
437
|
-
const PSTACK_RELEASE_OPT_OUT_VALUE = '0';
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Decide whether a full install also installs the pstack release.
|
|
441
|
-
*
|
|
442
|
-
* The step reaches the network for the pinned upstream commit. `--no-pstack`
|
|
443
|
-
* on the command line, or `CDE_INSTALL_PSTACK=0` in the environment, turns it
|
|
444
|
-
* off for an air-gapped or offline run.
|
|
445
|
-
*
|
|
446
|
-
* @param {string[]} [argumentList] The command-line arguments after the script.
|
|
447
|
-
* @param {Record<string, string|undefined>} [environment] The process environment.
|
|
448
|
-
* @returns {boolean} True when this run installs the pstack release.
|
|
449
|
-
*/
|
|
450
|
-
export function shouldInstallPstackRelease(
|
|
451
|
-
argumentList = process.argv.slice(2),
|
|
452
|
-
environment = process.env,
|
|
453
|
-
) {
|
|
454
|
-
if (argumentList.includes(PSTACK_RELEASE_OPT_OUT_FLAG)) return false;
|
|
455
|
-
return environment[PSTACK_RELEASE_OPT_OUT_VARIABLE] !== PSTACK_RELEASE_OPT_OUT_VALUE;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Install the pstack release into a managed root without failing the run.
|
|
460
|
-
*
|
|
461
|
-
* The pstack store keeps its own state and its own skill pointers, so this
|
|
462
|
-
* step reports its outcome rather than adding paths to the install manifest.
|
|
463
|
-
* A network failure returns a warning so the rules, hooks, and skills this
|
|
464
|
-
* run already wrote still reach their durable places.
|
|
465
|
-
*
|
|
466
|
-
* @param {object} [options] Options for `installPstack`, including `root`.
|
|
467
|
-
* @param {object} [dependencies] Seams `installPstack` accepts, such as `fetchSource`.
|
|
468
|
-
* @returns {{status: string, release: string|null, warning: string|null}} The outcome.
|
|
469
|
-
*/
|
|
470
|
-
export function installPstackRelease(options = {}, dependencies = {}) {
|
|
471
|
-
try {
|
|
472
|
-
const installation = installPstack(options, dependencies);
|
|
473
|
-
return {
|
|
474
|
-
status: installation.status,
|
|
475
|
-
release: installation.release,
|
|
476
|
-
warning: installation.warning ?? null,
|
|
477
|
-
};
|
|
478
|
-
} catch (installError) {
|
|
479
|
-
return { status: 'failed', release: null, warning: installError.message };
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
431
|
/**
|
|
484
|
-
*
|
|
432
|
+
* Install the pstack plugin from its marketplace into the hosts this run serves.
|
|
485
433
|
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
434
|
+
* The plugin carries its own skills, agents, and SessionStart hook, so the step
|
|
435
|
+
* reports its outcome rather than adding paths to the install manifest. A host
|
|
436
|
+
* without its command-line tool is skipped and a failing command is reported,
|
|
437
|
+
* so the rules, hooks, and skills this run already wrote still reach their
|
|
438
|
+
* durable places.
|
|
489
439
|
*
|
|
490
|
-
* @
|
|
491
|
-
* this run installs into.
|
|
492
|
-
* @returns {string|null} The manifest path when this run wrote it, else null.
|
|
440
|
+
* @returns {{status: string, hosts: object[], warning: string|null}} The outcome.
|
|
493
441
|
*/
|
|
494
|
-
export function
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (!outcome.didWrite) return null;
|
|
500
|
-
return join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function seedPstackPreferences(preferenceFileName) {
|
|
504
|
-
const preferenceDirectory = join(AGENTS_HOME, CURSOR_RULES_DIRECTORY_NAME);
|
|
505
|
-
const preferencePath = join(preferenceDirectory, preferenceFileName);
|
|
506
|
-
const preferenceSource = join(PACKAGE_ROOT, 'bin', preferenceFileName);
|
|
507
|
-
mkdirSync(preferenceDirectory, { recursive: true });
|
|
508
|
-
try {
|
|
509
|
-
copyFileSync(
|
|
510
|
-
preferenceSource,
|
|
511
|
-
preferencePath,
|
|
512
|
-
filesystemConstants.COPYFILE_EXCL,
|
|
513
|
-
);
|
|
514
|
-
return preferencePath;
|
|
515
|
-
} catch (copyError) {
|
|
516
|
-
if (copyError.code === 'EEXIST') return null;
|
|
517
|
-
throw copyError;
|
|
518
|
-
}
|
|
442
|
+
export function installPstackPluginForHosts() {
|
|
443
|
+
return installPstackPlugin({
|
|
444
|
+
claudeRoot: CLAUDE_HOME,
|
|
445
|
+
codexHome: INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
446
|
+
});
|
|
519
447
|
}
|
|
520
448
|
|
|
521
449
|
function seedSubagentModelPolicy() {
|
|
@@ -533,58 +461,30 @@ function seedSubagentModelPolicy() {
|
|
|
533
461
|
}
|
|
534
462
|
|
|
535
463
|
/**
|
|
536
|
-
*
|
|
537
|
-
* install root resolves. Without this step the companion ships to the agents
|
|
538
|
-
* home but no host ever calls it, so Poteto Mode only activates when someone
|
|
539
|
-
* remembers a second setup command.
|
|
464
|
+
* Removes the retired session-continuity registrations from the named hosts.
|
|
540
465
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
|
|
546
|
-
function registerSessionContinuityHooks() {
|
|
547
|
-
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
548
|
-
const hosts = Object.keys(configurationPaths)
|
|
549
|
-
.filter(host => existsSync(dirname(configurationPaths[host])));
|
|
550
|
-
if (hosts.length === 0) return [];
|
|
551
|
-
try {
|
|
552
|
-
const results = configureContinuityHosts(INSTALL_ROOT_RESOLUTION, hosts);
|
|
553
|
-
for (const result of results) {
|
|
554
|
-
const state = result.changed ? 'registered' : 'already registered';
|
|
555
|
-
console.log(` Session continuity: ${state} in ${result.path}`);
|
|
556
|
-
}
|
|
557
|
-
return results.map(result => result.path);
|
|
558
|
-
} catch (registrationError) {
|
|
559
|
-
console.warn(
|
|
560
|
-
` Warning: session-continuity hooks were not registered (${registrationError.message}) — `
|
|
561
|
-
+ 'run bin/install-session-continuity.mjs once the cause is resolved.',
|
|
562
|
-
);
|
|
563
|
-
return [];
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Removes the companion's registrations from the Codex and Cursor hook files
|
|
569
|
-
* during an uninstall. The Claude settings.json copy is pruned inside the
|
|
570
|
-
* settings block, which owns that file's single write.
|
|
571
|
-
*
|
|
572
|
-
* Each host this installer registered is a host it has to clean, or the host
|
|
573
|
-
* keeps calling a script the uninstall deleted.
|
|
466
|
+
* An earlier claude-dev-env registered the companion in Claude, Codex, and
|
|
467
|
+
* Cursor. The pstack plugin now carries the SessionStart context the companion
|
|
468
|
+
* supplied, so a registration left behind points every session at a deleted
|
|
469
|
+
* script. Every install clears all three hosts; an uninstall passes the two the
|
|
470
|
+
* settings block does not already own.
|
|
574
471
|
*
|
|
472
|
+
* @param {string[]} [hosts] The hosts to clear. Defaults to all three.
|
|
575
473
|
* @returns {string[]} The host configuration paths this run changed.
|
|
576
474
|
*/
|
|
577
|
-
function
|
|
475
|
+
function removeRetiredSessionContinuityHooks(hosts = ['claude', 'codex', 'cursor']) {
|
|
578
476
|
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
579
477
|
const changedPaths = [];
|
|
580
|
-
for (const host of
|
|
478
|
+
for (const host of hosts) {
|
|
581
479
|
const configurationPath = configurationPaths[host];
|
|
582
480
|
if (!existsSync(configurationPath)) continue;
|
|
583
|
-
const
|
|
481
|
+
const configurationText = readFileSync(configurationPath, 'utf8');
|
|
482
|
+
const configuration = JSON.parse(configurationText);
|
|
584
483
|
if (removeContinuityHooks(configuration) === 0) continue;
|
|
585
|
-
|
|
484
|
+
const indent = hostConfigurationIndent(configurationText);
|
|
485
|
+
writeFileSync(configurationPath, JSON.stringify(configuration, null, indent) + '\n');
|
|
586
486
|
changedPaths.push(configurationPath);
|
|
587
|
-
console.log(` Session continuity: hook registrations removed from ${configurationPath}`);
|
|
487
|
+
console.log(` Session continuity: retired hook registrations removed from ${configurationPath}`);
|
|
588
488
|
}
|
|
589
489
|
return changedPaths;
|
|
590
490
|
}
|
|
@@ -2811,20 +2711,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2811
2711
|
allInstalledFiles.push(...generatedCursorPaths);
|
|
2812
2712
|
summary.cursorRules = { created: generatedCursorPaths.length, updated: 0, paths: generatedCursorPaths };
|
|
2813
2713
|
syncWrittenPaths(allInstalledFiles);
|
|
2814
|
-
const pstackRuleSource = join(INSTALL_ROOT_RESOLUTION.cursorRulesInstallDirectory, PSTACK_MODEL_RULE_FILE_NAME);
|
|
2815
|
-
const sharedRuleDirectory = join(AGENTS_HOME, CURSOR_RULES_DIRECTORY_NAME);
|
|
2816
|
-
const sharedRulePath = join(sharedRuleDirectory, PSTACK_MODEL_RULE_FILE_NAME);
|
|
2817
|
-
mkdirSync(sharedRuleDirectory, { recursive: true });
|
|
2818
|
-
copyFileSync(pstackRuleSource, sharedRulePath);
|
|
2819
|
-
allInstalledFiles.push(sharedRulePath);
|
|
2820
|
-
syncWrittenPaths(allInstalledFiles);
|
|
2821
|
-
for (const eachPreferenceFileName of PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES) {
|
|
2822
|
-
const seededPreferencePath = seedPstackPreferences(eachPreferenceFileName);
|
|
2823
|
-
if (seededPreferencePath) {
|
|
2824
|
-
allInstalledFiles.push(seededPreferencePath);
|
|
2825
|
-
allUserOwnedPreferencePaths.add(seededPreferencePath);
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
2714
|
const seededPolicyPath = seedSubagentModelPolicy();
|
|
2829
2715
|
if (seededPolicyPath) {
|
|
2830
2716
|
allInstalledFiles.push(seededPolicyPath);
|
|
@@ -2860,16 +2746,28 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2860
2746
|
}
|
|
2861
2747
|
summary.skills = { created: skillsCreated, updated: skillsUpdated, pruned: 0, paths: skillPaths };
|
|
2862
2748
|
allInstalledFiles.push(...skillPaths);
|
|
2863
|
-
if (!selectedGroups
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2749
|
+
if (!selectedGroups) {
|
|
2750
|
+
summary.retiredSessionContinuity = {
|
|
2751
|
+
changedPaths: removeRetiredSessionContinuityHooks(),
|
|
2752
|
+
};
|
|
2753
|
+
const retiredPstack = removeRetiredPstackInstallation(INSTALL_ROOT_RESOLUTION);
|
|
2754
|
+
summary.retiredPstackStore = retiredPstack;
|
|
2755
|
+
for (const pointerPath of retiredPstack.removedPointerPaths) {
|
|
2756
|
+
console.log(` Pstack: retired pointer removed from ${pointerPath}`);
|
|
2757
|
+
}
|
|
2758
|
+
if (retiredPstack.removedStorePath) {
|
|
2759
|
+
console.log(` Pstack: retired release store removed from ${retiredPstack.removedStorePath}`);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
if (!selectedGroups && shouldInstallPstackPlugin()) {
|
|
2763
|
+
const pstackPlugin = installPstackPluginForHosts();
|
|
2764
|
+
summary.pstackPlugin = pstackPlugin;
|
|
2765
|
+
for (const eachHost of pstackPlugin.hosts) {
|
|
2766
|
+
console.log(eachHost.warning
|
|
2767
|
+
? ` Pstack (${eachHost.host}): ${eachHost.status} \u2014 ${eachHost.warning}`
|
|
2768
|
+
: ` Pstack (${eachHost.host}): ${eachHost.status}`);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2873
2771
|
syncWrittenPaths([...allInstalledFiles, ...publishedPointerPaths]);
|
|
2874
2772
|
throwIfFault(FAULT_PHASES.AFTER_FILE_STAGING);
|
|
2875
2773
|
throwIfFault(FAULT_PHASES.BEFORE_DURABLE_PROMOTION);
|
|
@@ -2999,9 +2897,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2999
2897
|
console.log(` \u2713 ${relative(CLAUDE_HOME, agentsHubDest)} (canonical guidance)`);
|
|
3000
2898
|
}
|
|
3001
2899
|
const isFullInstall = !selectedGroups;
|
|
3002
|
-
if (isFullInstall && shouldInstallAnyHooks) {
|
|
3003
|
-
summary.sessionContinuity = { configuredPaths: registerSessionContinuityHooks() };
|
|
3004
|
-
}
|
|
3005
2900
|
const didPruneRun = isFullInstall && UNRESOLVED_DEPENDENCY_NAMES.length === 0;
|
|
3006
2901
|
let failedPrunePaths = [];
|
|
3007
2902
|
let stalePrunedTotal = 0;
|
|
@@ -3268,7 +3163,7 @@ function executeUninstallPlan(plan, helpers = {}) {
|
|
|
3268
3163
|
if (!codexRoutingHookExists()) {
|
|
3269
3164
|
pruneManagedHooksFromHostConfiguration(CODEX_HOOKS_CONFIGURATION_PATH, 2);
|
|
3270
3165
|
}
|
|
3271
|
-
|
|
3166
|
+
removeRetiredSessionContinuityHooks(['codex', 'cursor']);
|
|
3272
3167
|
throwIfFault(FAULT_PHASES.AFTER_SETTINGS_WRITE);
|
|
3273
3168
|
|
|
3274
3169
|
unsetGlobalGitHooksPathIfOurs();
|
|
@@ -3394,7 +3289,7 @@ Usage:
|
|
|
3394
3289
|
npx ${PACKAGE_NAME} --target DIR Install into DIR instead of ~/.claude (overrides CLAUDE_CONFIG_DIR)
|
|
3395
3290
|
npx ${PACKAGE_NAME} --profile ID Install into one named profile root (under the profiles root)
|
|
3396
3291
|
npx ${PACKAGE_NAME} --profiles A,B Install into each selected profile (one ownership manifest per target)
|
|
3397
|
-
npx ${PACKAGE_NAME} --no-pstack Full install without the
|
|
3292
|
+
npx ${PACKAGE_NAME} --no-pstack Full install without the pstack plugin (also CDE_INSTALL_PSTACK=0)
|
|
3398
3293
|
npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
|
|
3399
3294
|
npx ${PACKAGE_NAME} --help Show this help
|
|
3400
3295
|
|
package/bin/install.test.mjs
CHANGED
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
readdirSync,
|
|
12
12
|
existsSync,
|
|
13
13
|
copyFileSync,
|
|
14
|
-
cpSync,
|
|
15
|
-
lstatSync,
|
|
16
14
|
} from 'node:fs';
|
|
17
15
|
import { tmpdir } from 'node:os';
|
|
18
16
|
import { join, dirname } from 'node:path';
|
|
@@ -20,8 +18,6 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
20
18
|
|
|
21
19
|
import {
|
|
22
20
|
collectPackageSourceConflicts,
|
|
23
|
-
installPstackRelease,
|
|
24
|
-
shouldInstallPstackRelease,
|
|
25
21
|
CONTENT_DIRECTORIES,
|
|
26
22
|
CORE_INCLUDE_DIRECTORIES,
|
|
27
23
|
CORE_SKILLS,
|
|
@@ -54,7 +50,6 @@ import {
|
|
|
54
50
|
hookCommandComparisonSpelling,
|
|
55
51
|
detectPython,
|
|
56
52
|
retainNewestRunBackupOnly,
|
|
57
|
-
refreshInstalledPstackPluginManifest,
|
|
58
53
|
} from './install.mjs';
|
|
59
54
|
import { EVER_SHIPPED_SKILL_NAMES } from './ever-shipped-skills.mjs';
|
|
60
55
|
|
|
@@ -2881,42 +2876,6 @@ test('pruneStaleInstalledFiles leaves the run backup root standing when the rena
|
|
|
2881
2876
|
}
|
|
2882
2877
|
});
|
|
2883
2878
|
|
|
2884
|
-
test('the pstack step writes a manifest for a skills root that carries pstack', () => {
|
|
2885
|
-
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-step-'));
|
|
2886
|
-
try {
|
|
2887
|
-
const skillDirectory = join(skillsRoot, 'pstack', 'how');
|
|
2888
|
-
mkdirSync(skillDirectory, { recursive: true });
|
|
2889
|
-
writeFileSync(join(skillDirectory, 'SKILL.md'), '---\nname: how\n---\n');
|
|
2890
|
-
|
|
2891
|
-
const manifestPath = refreshInstalledPstackPluginManifest(skillsRoot);
|
|
2892
|
-
|
|
2893
|
-
assert.equal(
|
|
2894
|
-
manifestPath,
|
|
2895
|
-
join(skillsRoot, 'pstack', '.claude-plugin', 'plugin.json'),
|
|
2896
|
-
'the step reports the manifest it wrote so the run records the path',
|
|
2897
|
-
);
|
|
2898
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
2899
|
-
assert.deepEqual(manifest.skills, ['./how']);
|
|
2900
|
-
assert.equal(
|
|
2901
|
-
refreshInstalledPstackPluginManifest(skillsRoot),
|
|
2902
|
-
null,
|
|
2903
|
-
'a second run writes nothing and reports no path',
|
|
2904
|
-
);
|
|
2905
|
-
} finally {
|
|
2906
|
-
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2907
|
-
}
|
|
2908
|
-
});
|
|
2909
|
-
|
|
2910
|
-
test('the pstack step reports no path for a skills root without pstack', () => {
|
|
2911
|
-
const skillsRoot = mkdtempSync(join(tmpdir(), 'cdev-pstack-absent-'));
|
|
2912
|
-
try {
|
|
2913
|
-
assert.equal(refreshInstalledPstackPluginManifest(skillsRoot), null);
|
|
2914
|
-
assert.equal(existsSync(join(skillsRoot, 'pstack')), false);
|
|
2915
|
-
} finally {
|
|
2916
|
-
rmSync(skillsRoot, { recursive: true, force: true });
|
|
2917
|
-
}
|
|
2918
|
-
});
|
|
2919
|
-
|
|
2920
2879
|
const RETIRED_SKILLS = ['pr-small-cl', 'session-log', 'session-tidy'];
|
|
2921
2880
|
|
|
2922
2881
|
test('each core skill has a shipped entry point', () => {
|
|
@@ -2934,13 +2893,9 @@ test('retired skills leave install groups and stay in the cleanup registry', ()
|
|
|
2934
2893
|
}
|
|
2935
2894
|
});
|
|
2936
2895
|
|
|
2937
|
-
test('README
|
|
2896
|
+
test('the README names current session tools', () => {
|
|
2938
2897
|
const readme = readFileSync(new URL('../../../README.md', import.meta.url), 'utf8');
|
|
2939
|
-
const vaultRule = readFileSync(new URL('../rules/vault-context.md', import.meta.url), 'utf8');
|
|
2940
2898
|
assert.doesNotMatch(readme, /--only journal|session-log|session-tidy/);
|
|
2941
|
-
assert.doesNotMatch(vaultRule, /`session-log`|`recall`/);
|
|
2942
|
-
assert.match(vaultRule, /obsidian MCP tools/);
|
|
2943
|
-
assert.match(vaultRule, /project.*frontmatter/);
|
|
2944
2899
|
});
|
|
2945
2900
|
|
|
2946
2901
|
test('the help output states that the installer reads only flags', () => {
|
|
@@ -2980,43 +2935,24 @@ test('pruneRetiredHookEntriesFromSettings warns and returns 0 when the host conf
|
|
|
2980
2935
|
|
|
2981
2936
|
const PSTACK_TEST_PACKAGE_ROOT = dirname(fileURLToPath(new URL('./install.mjs', import.meta.url)));
|
|
2982
2937
|
const PSTACK_TEST_INSTALLER_PATH = fileURLToPath(new URL('./install.mjs', import.meta.url));
|
|
2983
|
-
const PSTACK_TEST_LOCK = JSON.parse(readFileSync(
|
|
2984
|
-
new URL('../scripts/pstack.lock.json', import.meta.url),
|
|
2985
|
-
'utf8',
|
|
2986
|
-
));
|
|
2987
|
-
|
|
2988
|
-
function writePstackFixtureFile(filePath, fileContent) {
|
|
2989
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
2990
|
-
writeFileSync(filePath, fileContent);
|
|
2991
|
-
}
|
|
2992
2938
|
|
|
2993
|
-
function
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
return
|
|
3007
|
-
managedRoot,
|
|
3008
|
-
options: {
|
|
3009
|
-
root: managedRoot,
|
|
3010
|
-
lock: structuredClone(PSTACK_TEST_LOCK),
|
|
3011
|
-
packageRoot: dirname(PSTACK_TEST_PACKAGE_ROOT),
|
|
3012
|
-
},
|
|
3013
|
-
dependencies: {
|
|
3014
|
-
fetchSource: (_lock, destination) => cpSync(checkout, destination, { recursive: true }),
|
|
3015
|
-
},
|
|
3016
|
-
};
|
|
2939
|
+
function writeRecordingHostCommand(directory, name, logPath) {
|
|
2940
|
+
mkdirSync(directory, { recursive: true });
|
|
2941
|
+
if (process.platform === 'win32') {
|
|
2942
|
+
const batchPath = join(directory, `${name}.cmd`);
|
|
2943
|
+
writeFileSync(batchPath, `@echo off\r\n>>"${logPath}" echo ${name} %*\r\n`);
|
|
2944
|
+
return batchPath;
|
|
2945
|
+
}
|
|
2946
|
+
const commandPath = join(directory, name);
|
|
2947
|
+
writeFileSync(
|
|
2948
|
+
commandPath,
|
|
2949
|
+
`#!/bin/sh\nprintf '%s %s\\n' "${name}" "$*" >> "${logPath}"\n`,
|
|
2950
|
+
{ mode: 0o755 },
|
|
2951
|
+
);
|
|
2952
|
+
return commandPath;
|
|
3017
2953
|
}
|
|
3018
2954
|
|
|
3019
|
-
function runPstackInstaller(homeDirectory, extraArguments) {
|
|
2955
|
+
function runPstackInstaller(homeDirectory, extraArguments, environmentOverrides = {}) {
|
|
3020
2956
|
return execFileSync('node', [PSTACK_TEST_INSTALLER_PATH, ...extraArguments], {
|
|
3021
2957
|
cwd: dirname(PSTACK_TEST_PACKAGE_ROOT),
|
|
3022
2958
|
encoding: 'utf8',
|
|
@@ -3028,82 +2964,80 @@ function runPstackInstaller(homeDirectory, extraArguments) {
|
|
|
3028
2964
|
CLAUDE_CONFIG_DIR: join(homeDirectory, '.claude'),
|
|
3029
2965
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
3030
2966
|
CDE_INSTALL_PSTACK: '1',
|
|
3031
|
-
|
|
2967
|
+
...environmentOverrides,
|
|
3032
2968
|
},
|
|
3033
2969
|
});
|
|
3034
2970
|
}
|
|
3035
2971
|
|
|
3036
|
-
function
|
|
2972
|
+
function pstackPluginSandbox(t) {
|
|
3037
2973
|
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-install-pstack-run-'));
|
|
3038
2974
|
t.after(() => rmSync(homeDirectory, { recursive: true, force: true }));
|
|
3039
|
-
|
|
2975
|
+
const commandLogPath = join(homeDirectory, 'host-commands.log');
|
|
2976
|
+
const commandDirectory = join(homeDirectory, 'host-commands');
|
|
2977
|
+
return {
|
|
2978
|
+
homeDirectory,
|
|
2979
|
+
commandLogPath,
|
|
2980
|
+
environment: {
|
|
2981
|
+
CDE_CLAUDE_EXECUTABLE: writeRecordingHostCommand(commandDirectory, 'claude', commandLogPath),
|
|
2982
|
+
CDE_CODEX_EXECUTABLE: writeRecordingHostCommand(commandDirectory, 'codex', commandLogPath),
|
|
2983
|
+
},
|
|
2984
|
+
recordedCommands() {
|
|
2985
|
+
if (!existsSync(commandLogPath)) return [];
|
|
2986
|
+
return readFileSync(commandLogPath, 'utf8')
|
|
2987
|
+
.split(/\r?\n/)
|
|
2988
|
+
.map(eachLine => eachLine.trim())
|
|
2989
|
+
.filter(Boolean);
|
|
2990
|
+
},
|
|
2991
|
+
};
|
|
3040
2992
|
}
|
|
3041
2993
|
|
|
3042
|
-
test('
|
|
3043
|
-
const
|
|
2994
|
+
test('a full install adds the pstack marketplace and plugin on both hosts', t => {
|
|
2995
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3044
2996
|
|
|
3045
|
-
const
|
|
2997
|
+
const installerOutput = runPstackInstaller(sandbox.homeDirectory, [], sandbox.environment);
|
|
3046
2998
|
|
|
3047
|
-
assert.
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
2999
|
+
assert.deepEqual(sandbox.recordedCommands(), [
|
|
3000
|
+
'claude plugin marketplace add michael-denyer/pstack-claude',
|
|
3001
|
+
'claude plugin install pstack@pstack-claude',
|
|
3002
|
+
'codex plugin marketplace add michael-denyer/pstack-claude',
|
|
3003
|
+
'codex plugin add pstack@pstack-claude',
|
|
3004
|
+
]);
|
|
3005
|
+
assert.match(installerOutput, /Pstack \(claude\): installed/);
|
|
3006
|
+
assert.match(installerOutput, /Pstack \(codex\): installed/);
|
|
3053
3007
|
});
|
|
3054
3008
|
|
|
3055
|
-
test('an
|
|
3056
|
-
const
|
|
3009
|
+
test('an absent host command is reported and leaves the other host installed', t => {
|
|
3010
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3057
3011
|
|
|
3058
|
-
const
|
|
3059
|
-
|
|
3012
|
+
const installerOutput = runPstackInstaller(sandbox.homeDirectory, [], {
|
|
3013
|
+
...sandbox.environment,
|
|
3014
|
+
CDE_CODEX_EXECUTABLE: join(sandbox.homeDirectory, 'host-commands', 'absent-codex-command'),
|
|
3060
3015
|
});
|
|
3061
3016
|
|
|
3062
|
-
assert.
|
|
3063
|
-
assert.
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
assert.
|
|
3069
|
-
assert.
|
|
3070
|
-
|
|
3071
|
-
|
|
3017
|
+
assert.match(installerOutput, /Pstack \(claude\): installed/);
|
|
3018
|
+
assert.match(
|
|
3019
|
+
installerOutput,
|
|
3020
|
+
/Pstack \(codex\): (skipped|failed)/,
|
|
3021
|
+
'the absent host is reported either way, and the two unit tests pin which signal maps to skipped',
|
|
3022
|
+
);
|
|
3023
|
+
assert.doesNotMatch(installerOutput, /Pstack \(codex\): installed/);
|
|
3024
|
+
assert.deepEqual(
|
|
3025
|
+
sandbox.recordedCommands().filter(eachCommand => eachCommand.startsWith('codex')),
|
|
3026
|
+
[],
|
|
3027
|
+
);
|
|
3072
3028
|
});
|
|
3073
3029
|
|
|
3074
|
-
test('
|
|
3075
|
-
|
|
3076
|
-
const installerOutput = runPstackInstaller(homeDirectory, []);
|
|
3077
|
-
|
|
3078
|
-
assert.match(installerOutput, /Pstack:/, 'the run reports the pstack step');
|
|
3079
|
-
assert.equal(existsSync(join(homeDirectory, '.claude', 'pstack')), true);
|
|
3080
|
-
});
|
|
3081
|
-
});
|
|
3030
|
+
test('--no-pstack leaves both hosts untouched', t => {
|
|
3031
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3082
3032
|
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3033
|
+
const installerOutput = runPstackInstaller(
|
|
3034
|
+
sandbox.homeDirectory, ['--no-pstack'], sandbox.environment,
|
|
3035
|
+
);
|
|
3086
3036
|
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
});
|
|
3037
|
+
assert.deepEqual(sandbox.recordedCommands(), []);
|
|
3038
|
+
assert.doesNotMatch(installerOutput, /Pstack \(/);
|
|
3090
3039
|
});
|
|
3091
3040
|
|
|
3092
|
-
function runContinuityInstaller(homeDirectory, extraArguments) {
|
|
3093
|
-
return execFileSync('node', [PSTACK_TEST_INSTALLER_PATH, ...extraArguments], {
|
|
3094
|
-
cwd: dirname(PSTACK_TEST_PACKAGE_ROOT),
|
|
3095
|
-
encoding: 'utf8',
|
|
3096
|
-
env: {
|
|
3097
|
-
...process.env,
|
|
3098
|
-
CDE_INSTALL_PSTACK: '0',
|
|
3099
|
-
HOME: homeDirectory,
|
|
3100
|
-
USERPROFILE: homeDirectory,
|
|
3101
|
-
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
3102
|
-
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
3103
|
-
},
|
|
3104
|
-
});
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
3041
|
function continuityCommandCount(configurationPath) {
|
|
3108
3042
|
if (!existsSync(configurationPath)) return 0;
|
|
3109
3043
|
const configuration = JSON.parse(readFileSync(configurationPath, 'utf8'));
|
|
@@ -3116,32 +3050,59 @@ function continuityCommandCount(configurationPath) {
|
|
|
3116
3050
|
).length;
|
|
3117
3051
|
}
|
|
3118
3052
|
|
|
3119
|
-
test('a full install
|
|
3120
|
-
const
|
|
3121
|
-
|
|
3122
|
-
const
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3053
|
+
test('a full install removes the retired pstack pointers and release store', t => {
|
|
3054
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3055
|
+
const managedRoot = join(sandbox.homeDirectory, '.claude');
|
|
3056
|
+
const agentsSkillsRoot = join(sandbox.homeDirectory, '.agents', 'skills');
|
|
3057
|
+
const releaseSkillsRoot = join(managedRoot, 'pstack', 'releases', 'abc123', 'runtime', 'pstack', 'skills');
|
|
3058
|
+
mkdirSync(join(releaseSkillsRoot, 'poteto-mode'), { recursive: true });
|
|
3059
|
+
mkdirSync(join(managedRoot, 'skills'), { recursive: true });
|
|
3060
|
+
mkdirSync(agentsSkillsRoot, { recursive: true });
|
|
3061
|
+
const allPointerPaths = [
|
|
3062
|
+
join(managedRoot, 'skills', 'pstack'),
|
|
3063
|
+
join(agentsSkillsRoot, 'pstack'),
|
|
3126
3064
|
];
|
|
3065
|
+
for (const pointerPath of allPointerPaths) {
|
|
3066
|
+
symlinkSync(releaseSkillsRoot, pointerPath, 'dir');
|
|
3067
|
+
}
|
|
3127
3068
|
|
|
3128
|
-
|
|
3069
|
+
const installerOutput = runPstackInstaller(sandbox.homeDirectory, [], sandbox.environment);
|
|
3129
3070
|
|
|
3130
|
-
for (const
|
|
3071
|
+
for (const pointerPath of allPointerPaths) {
|
|
3131
3072
|
assert.equal(
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
`the install
|
|
3073
|
+
existsSync(pointerPath),
|
|
3074
|
+
false,
|
|
3075
|
+
`the install removes ${pointerPath}, which Claude Code would load as a second pstack plugin`,
|
|
3135
3076
|
);
|
|
3136
3077
|
}
|
|
3078
|
+
assert.equal(existsSync(join(managedRoot, 'pstack')), false);
|
|
3079
|
+
assert.match(installerOutput, /Pstack: retired release store removed/);
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3082
|
+
test('a full install clears a retired session-continuity registration from every host', t => {
|
|
3083
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3084
|
+
const companionCommand = 'node "/stale/.agents/skills/session-continuity/continuity.mjs" hook claude';
|
|
3085
|
+
const allConfigurationPaths = [
|
|
3086
|
+
join(sandbox.homeDirectory, '.claude', 'settings.json'),
|
|
3087
|
+
join(sandbox.homeDirectory, '.codex', 'hooks.json'),
|
|
3088
|
+
join(sandbox.homeDirectory, '.cursor', 'hooks.json'),
|
|
3089
|
+
];
|
|
3090
|
+
for (const configurationPath of allConfigurationPaths) {
|
|
3091
|
+
mkdirSync(dirname(configurationPath), { recursive: true });
|
|
3092
|
+
writeFileSync(configurationPath, JSON.stringify({
|
|
3093
|
+
hooks: {
|
|
3094
|
+
SessionStart: [{ hooks: [{ type: 'command', command: companionCommand }] }],
|
|
3095
|
+
},
|
|
3096
|
+
}, null, 2) + '\n');
|
|
3097
|
+
}
|
|
3137
3098
|
|
|
3138
|
-
|
|
3099
|
+
runPstackInstaller(sandbox.homeDirectory, [], sandbox.environment);
|
|
3139
3100
|
|
|
3140
3101
|
for (const configurationPath of allConfigurationPaths) {
|
|
3141
3102
|
assert.equal(
|
|
3142
3103
|
continuityCommandCount(configurationPath),
|
|
3143
3104
|
0,
|
|
3144
|
-
`
|
|
3105
|
+
`the install leaves no registration in ${configurationPath} pointing at a removed script`,
|
|
3145
3106
|
);
|
|
3146
3107
|
}
|
|
3147
3108
|
});
|