create-principles-disciple 1.121.9 → 1.121.10

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.
@@ -9,6 +9,7 @@ import { checkOpenClawGateway, stopOpenClawGateway, restartOpenClawGateway, } fr
9
9
  import { migrateLegacyExtensionBackups, reservePdBackupDestination, resolvePdBackupsRoot, } from '../utils/pd-backups.js';
10
10
  import { ActivationCompatibilityReadModel } from '@principles/core/runtime-v2';
11
11
  import { getInstallLayoutPaths, resolveInstallLayout } from '@principles/install-layout';
12
+ import { collectFileDepLinkSpecs } from '../utils/update-links.js';
12
13
  /**
13
14
  * Legacy rule contract preflight (2026-08-19): refuse to swap the runtime
14
15
  * while an ACTIVE owner-approved rule still depends on a RuleHost contract
@@ -724,22 +725,35 @@ function depsMeaningfullyChanged(oldDeps, newDeps) {
724
725
  return aKeys.some((k, i) => bKeys[i] !== k || a[k] !== b[k]);
725
726
  }
726
727
  /**
727
- * Create the node_modules/@principles/host-runtime resolution links for the
728
- * installed console and pd-cli packages, if missing.
728
+ * Create the node_modules resolution links for the installed components, if
729
+ * missing.
729
730
  *
730
731
  * Mirrors installer.ts syncPdCli: junction on Windows (no elevation needed),
731
732
  * relative symlink elsewhere. Fresh installs get these links via npm install
732
- * (the bundled package.json rewrites the dep to file:../host-runtime); the
733
- * full update deliberately skips npm install, so it must create them itself.
734
- * Without the link, the updated console dist which statically imports
735
- * @principles/host-runtime since 2026-08-21 (41cf97ee5) — crashes at startup
736
- * with ERR_MODULE_NOT_FOUND on installs created before the installer bundled
737
- * host-runtime (PRI-561).
733
+ * (the bundled package.json rewrites internal deps to file:../<component>);
734
+ * the full update deliberately skips npm install, so it must create them
735
+ * itself. Without a link, updated dists crash at startup with
736
+ * ERR_MODULE_NOT_FOUND (host-runtime: PRI-561, 2026-08-21).
737
+ *
738
+ * Two sources, both fail-closed on creation errors:
739
+ * 1. the explicit link list below (known-critical links: a missing one
740
+ * means the updated console cannot start);
741
+ * 2. a data-driven pass that derives links from the STAGED manifests —
742
+ * the freshly extracted release trees under tempDir. Their `file:`
743
+ * declarations are the authoritative list of links the updated tree
744
+ * needs. Reading the deployed (pre-update) manifests instead would
745
+ * miss every dependency this release newly introduced: the running
746
+ * console executes update logic from its own dist (one generation
747
+ * behind the components it installs) — observed 2026-08-29 when the
748
+ * 1.221.2 console updated hosts to 1.222.5 but could not know about
749
+ * the newly introduced install-layout component, leaving
750
+ * host-runtime/node_modules/@principles/install-layout missing and
751
+ * every pd-cli runtime command failing with ERR_MODULE_NOT_FOUND.
738
752
  *
739
753
  * Returns undefined on success, or an error message (rc-9: observable, never
740
754
  * silent — a missing link means the updated console cannot start).
741
755
  */
742
- function ensureRuntimeResolutionLinks(layout) {
756
+ function ensureRuntimeResolutionLinks(layout, tempDir) {
743
757
  const links = [
744
758
  { linkPath: path.join(layout.consoleDir, 'node_modules', '@principles', 'host-runtime'), target: layout.hostRuntimeDir },
745
759
  { linkPath: path.join(layout.pdCliDir, 'node_modules', '@principles', 'host-runtime'), target: layout.hostRuntimeDir },
@@ -750,11 +764,43 @@ function ensureRuntimeResolutionLinks(layout) {
750
764
  { linkPath: path.join(layout.hostRuntimeDir, 'node_modules', '@principles', 'install-layout'), target: layout.installLayoutDir },
751
765
  { linkPath: path.join(layout.consoleDir, 'node_modules', 'principles-disciple'), target: layout.pluginDir },
752
766
  ];
753
- for (const { linkPath, target } of links) {
754
- if (!fs.existsSync(target))
755
- continue;
767
+ // Data-driven pass: derive links from the STAGED component manifests (see
768
+ // the comment above — staged, never the deployed pre-update manifests).
769
+ const stagedComponents = [
770
+ { manifestDir: path.join(tempDir, 'console'), deployedDir: layout.consoleDir },
771
+ { manifestDir: path.join(tempDir, 'pd-cli'), deployedDir: layout.pdCliDir },
772
+ { manifestDir: path.join(tempDir, 'host-runtime'), deployedDir: layout.hostRuntimeDir },
773
+ { manifestDir: path.join(tempDir, 'install-layout'), deployedDir: layout.installLayoutDir },
774
+ { manifestDir: path.join(tempDir, 'core'), deployedDir: layout.coreDir },
775
+ { manifestDir: path.join(tempDir, 'plugin'), deployedDir: layout.pluginDir },
776
+ ];
777
+ const readStagedDependencies = (manifestDir) => {
778
+ try {
779
+ const pkgPath = path.join(manifestDir, 'package.json');
780
+ if (!fs.existsSync(pkgPath))
781
+ return {};
782
+ const parsed = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
783
+ if (!isRecord(parsed) || !isRecord(parsed.dependencies))
784
+ return {};
785
+ const out = {};
786
+ for (const [name, ref] of Object.entries(parsed.dependencies)) {
787
+ if (typeof ref === 'string')
788
+ out[name] = ref;
789
+ }
790
+ return out;
791
+ }
792
+ catch {
793
+ // rc-9: an unreadable staged manifest degrades to "no derived links" —
794
+ // the explicit list above still covers the known-critical links.
795
+ return {};
796
+ }
797
+ };
798
+ const fileDepSpecs = collectFileDepLinkSpecs(stagedComponents, readStagedDependencies);
799
+ const createResolutionLink = (linkPath, target) => {
800
+ // Idempotent: never overwrite an existing link or directory (fresh
801
+ // installs have npm-created real dirs in these slots).
756
802
  if (fs.existsSync(linkPath))
757
- continue;
803
+ return undefined;
758
804
  try {
759
805
  fs.mkdirSync(path.dirname(linkPath), { recursive: true });
760
806
  if (process.platform === 'win32') {
@@ -763,10 +809,32 @@ function ensureRuntimeResolutionLinks(layout) {
763
809
  else {
764
810
  fs.symlinkSync(path.relative(path.dirname(linkPath), target), linkPath, 'dir');
765
811
  }
812
+ return undefined;
766
813
  }
767
814
  catch (error) {
768
815
  return `Failed to create runtime resolution link at ${linkPath}: ${error instanceof Error ? error.message : String(error)}`;
769
816
  }
817
+ };
818
+ // Pass 1 — explicit known-critical links, fail-closed BEFORE any byte is
819
+ // swapped (a link-creation failure aborts with the installed packages
820
+ // untouched: the PRI-561 ordering contract).
821
+ for (const { linkPath, target } of links) {
822
+ if (!fs.existsSync(target))
823
+ continue;
824
+ const error = createResolutionLink(linkPath, target);
825
+ if (error)
826
+ return error;
827
+ }
828
+ // Pass 2 — data-driven derived links. Their deployed target dir may be
829
+ // created by the copy steps that follow (a brand-new component's dir does
830
+ // not exist yet); each staged target's existence was already proven by the
831
+ // extraction, and the copies below run unconditionally.
832
+ for (const spec of fileDepSpecs) {
833
+ if (fs.existsSync(spec.linkPath))
834
+ continue;
835
+ const error = createResolutionLink(spec.linkPath, spec.target);
836
+ if (error)
837
+ return error;
770
838
  }
771
839
  return undefined;
772
840
  }
@@ -935,7 +1003,7 @@ async function doInlineFullUpdate(workspaceDir) {
935
1003
  if (fs.existsSync(path.join(installLayoutSrc, 'package.json')) && fs.existsSync(path.join(installLayoutSrc, 'dist'))) {
936
1004
  copyDirRecursive(installLayoutSrc, layout.installLayoutDir, SKIP_DIRS);
937
1005
  }
938
- const linkError = ensureRuntimeResolutionLinks(layout);
1006
+ const linkError = ensureRuntimeResolutionLinks(layout, tempDir);
939
1007
  if (linkError) {
940
1008
  appendUpdateHistory(workspaceDir, {
941
1009
  fromVersion,
@@ -0,0 +1,38 @@
1
+ export type FileDepLinkSpec = {
2
+ linkPath: string;
3
+ /** Deployed layout dir the link points at; may be created by the copy
4
+ * steps that run after derivation (see stagedTargetExists). */
5
+ target: string;
6
+ /** The staged sibling dir proving this component ships in this release. */
7
+ stagedTarget: string;
8
+ };
9
+ export type StagedComponent = {
10
+ /** Freshly extracted component dir under the update temp dir. */
11
+ manifestDir: string;
12
+ /** Deployed layout dir this component is (or will be) installed into. */
13
+ deployedDir: string;
14
+ };
15
+ /**
16
+ * Derive the node_modules link specs implied by each staged component's
17
+ * declared `file:` dependencies.
18
+ *
19
+ * - `readDependencies(manifestDir)` returns the component's dependency map
20
+ * (name -> version-or-ref); return {} for unreadable/missing manifests.
21
+ * - A `file:` ref is resolved against its staged manifest dir to find the
22
+ * staged sibling target; the deployed link target is taken from the same
23
+ * staged->deployed pairing the caller provides — component identity is
24
+ * the staged directory itself (normalized absolute path), NOT the
25
+ * deployed directory's basename. In the legacy layout the deployed
26
+ * plugin dir is named `principles-disciple` while the staged one is
27
+ * `plugin`, so a basename lookup would silently drop the derived link
28
+ * (review P1, PR #1457).
29
+ * - Deps whose staged target is not one of the layout components (e.g.
30
+ * @principles/codex-adapter — a separate host install, not part of this
31
+ * layout) are skipped.
32
+ *
33
+ * Note: `target` (the deployed dir) may not exist yet at derivation time —
34
+ * for components whose copy step runs after this call, the caller creates
35
+ * the directory right after deriving and creating these links. Use
36
+ * `stagedTarget` to verify the component actually ships in this release.
37
+ */
38
+ export declare function collectFileDepLinkSpecs(stagedComponents: readonly StagedComponent[], readDependencies: (manifestDir: string) => Record<string, string>): FileDepLinkSpec[];
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Data-driven resolution-link derivation for the full-update pipeline.
3
+ *
4
+ * The deployed console performs full updates with the update logic baked
5
+ * into its own dist — one generation behind the components it installs. A
6
+ * hardcoded link list therefore goes stale the moment a release introduces
7
+ * a new internal `file:` dependency (observed 2026-08-29: the 1.221.2
8
+ * console could not know about the newly introduced install-layout
9
+ * component, so the 1.222.5 update left
10
+ * host-runtime/node_modules/@principles/install-layout missing and every
11
+ * pd-cli runtime command failed with ERR_MODULE_NOT_FOUND).
12
+ *
13
+ * The authoritative source for "which links does the UPDATED tree need" is
14
+ * the STAGED manifests under the update's temp dir (the freshly extracted
15
+ * release trees) — NOT the deployed pre-update manifests, which by
16
+ * definition do not declare newly added dependencies.
17
+ *
18
+ * Each staged component's `file:` refs resolve to a sibling staged
19
+ * component directory; the deployed link target is that sibling's entry in
20
+ * the same staged->deployed pairing.
21
+ */
22
+ import * as path from 'node:path';
23
+ /**
24
+ * Derive the node_modules link specs implied by each staged component's
25
+ * declared `file:` dependencies.
26
+ *
27
+ * - `readDependencies(manifestDir)` returns the component's dependency map
28
+ * (name -> version-or-ref); return {} for unreadable/missing manifests.
29
+ * - A `file:` ref is resolved against its staged manifest dir to find the
30
+ * staged sibling target; the deployed link target is taken from the same
31
+ * staged->deployed pairing the caller provides — component identity is
32
+ * the staged directory itself (normalized absolute path), NOT the
33
+ * deployed directory's basename. In the legacy layout the deployed
34
+ * plugin dir is named `principles-disciple` while the staged one is
35
+ * `plugin`, so a basename lookup would silently drop the derived link
36
+ * (review P1, PR #1457).
37
+ * - Deps whose staged target is not one of the layout components (e.g.
38
+ * @principles/codex-adapter — a separate host install, not part of this
39
+ * layout) are skipped.
40
+ *
41
+ * Note: `target` (the deployed dir) may not exist yet at derivation time —
42
+ * for components whose copy step runs after this call, the caller creates
43
+ * the directory right after deriving and creating these links. Use
44
+ * `stagedTarget` to verify the component actually ships in this release.
45
+ */
46
+ export function collectFileDepLinkSpecs(stagedComponents, readDependencies) {
47
+ const deployedDirByStagedDir = new Map();
48
+ for (const component of stagedComponents) {
49
+ deployedDirByStagedDir.set(path.resolve(component.manifestDir), component.deployedDir);
50
+ }
51
+ const specs = [];
52
+ const seen = new Set();
53
+ for (const component of stagedComponents) {
54
+ for (const [name, ref] of Object.entries(readDependencies(component.manifestDir))) {
55
+ if (typeof ref !== 'string' || !ref.startsWith('file:'))
56
+ continue;
57
+ const stagedTarget = path.resolve(component.manifestDir, ref.slice('file:'.length));
58
+ const deployedTarget = deployedDirByStagedDir.get(stagedTarget);
59
+ if (deployedTarget === undefined)
60
+ continue;
61
+ // The link lives in the DEPLOYED component dir (what the updated dist
62
+ // resolves against) — never inside the throwaway temp dir.
63
+ const linkPath = path.join(component.deployedDir, 'node_modules', name);
64
+ if (seen.has(linkPath))
65
+ continue;
66
+ seen.add(linkPath);
67
+ specs.push({ linkPath, target: deployedTarget, stagedTarget });
68
+ }
69
+ }
70
+ return specs;
71
+ }
package/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/core",
3
- "version": "1.270.5",
3
+ "version": "1.74.1",
4
4
  "description": "Pure-logic core of Principles Disciple, an AI Agent Governance System - pain signal capture and principle proposal (no I/O)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/host-runtime",
3
- "version": "0.1.1",
3
+ "version": "0.1.0",
4
4
  "description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/install-layout",
3
- "version": "0.1.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,17 +10,13 @@
10
10
  "default": "./dist/index.js"
11
11
  }
12
12
  },
13
- "files": [
14
- "dist"
15
- ],
13
+ "files": ["dist"],
16
14
  "license": "MIT",
17
15
  "repository": {
18
16
  "type": "git",
19
17
  "url": "git+https://github.com/csuzngjh/principles.git"
20
18
  },
21
- "publishConfig": {
22
- "access": "public"
23
- },
19
+ "publishConfig": { "access": "public" },
24
20
  "scripts": {
25
21
  "build": "tsc",
26
22
  "test": "npm run build && vitest run"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.121.9",
3
+ "version": "1.121.10",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/pd-cli",
3
- "version": "1.142.4",
3
+ "version": "1.74.1",
4
4
  "description": "PD CLI — Pain recording, sample management, and governance tasks for Principles Disciple",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Principles Disciple is an AI Agent Governance System. Stop correcting the same AI behavior across sessions. Turn repeated Agent corrections into Owner-approved, observable, reversible behavior principles.",
5
- "version": "1.222.5",
5
+ "version": "1.198.1",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"