@pieai/pro-gov 0.3.6 → 0.3.7

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.
@@ -129,6 +129,12 @@ The plan file is the safety gate. Review it before applying. It creates managed
129
129
  symlinks and `.pro-gov/assets.lock.json`; it should not overwrite unmanaged
130
130
  project files.
131
131
 
132
+ Managed symlinks are relative by default. A target project's normal
133
+ `assets check` validates the local lock and linked content without requiring the
134
+ public package to know a maintainer's private registry. Maintainers can add
135
+ `--strict-registry`, or run `pro-gov portfolio assets-check --config
136
+ /path/to/portfolio.json`, when they need central private-registry validation.
137
+
132
138
  When a maintainer promotes a private asset into `public-agent-assets/`, the
133
139
  public registry must record the private-source hash and the public-copy hash.
134
140
  Run this in the upstream checkout before publishing:
package/dist/cli.js CHANGED
@@ -422,6 +422,7 @@ function listFiles3(absolutePath) {
422
422
  if (stats.isFile()) return [absolutePath];
423
423
  const files = [];
424
424
  for (const entry of readdirSync4(absolutePath, { withFileTypes: true })) {
425
+ if (shouldIgnoreAssetHashEntry(entry.name)) continue;
425
426
  const entryPath = join5(absolutePath, entry.name);
426
427
  if (entry.isDirectory()) {
427
428
  files.push(...listFiles3(entryPath));
@@ -431,6 +432,9 @@ function listFiles3(absolutePath) {
431
432
  }
432
433
  return files.sort();
433
434
  }
435
+ function shouldIgnoreAssetHashEntry(name) {
436
+ return name === "__pycache__" || name === ".DS_Store" || name === "Thumbs.db" || name === "node_modules" || name.endsWith(".pyc") || name.endsWith(".pyo");
437
+ }
434
438
  function toUnixPath3(path) {
435
439
  return path.replaceAll("\\", "/");
436
440
  }
@@ -532,8 +536,8 @@ function resolveSafePath(root, sourcePath) {
532
536
  }
533
537
 
534
538
  // src/asset-targets/apply.ts
535
- import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
536
- import { dirname as dirname3, join as join7, resolve } from "node:path";
539
+ import { existsSync as existsSync7, lstatSync as lstatSync2, mkdirSync as mkdirSync2, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
540
+ import { dirname as dirname3, join as join7, relative as relative4, resolve } from "node:path";
537
541
  function applyAssetInstallPlan(plan) {
538
542
  const appliedActions = [];
539
543
  for (const action of plan.actions) {
@@ -555,15 +559,16 @@ function applyAction(targetDir, action) {
555
559
  }
556
560
  mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
557
561
  const sourceAbsolutePath = resolve(action.sourcePath);
562
+ const symlinkTarget = relative4(realpathSync(dirname3(targetAbsolutePath)), realpathSync(sourceAbsolutePath)) || ".";
558
563
  if (action.type === "symlink") {
559
564
  if (pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
560
565
  throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
561
566
  }
562
- symlinkSync(sourceAbsolutePath, targetAbsolutePath);
567
+ symlinkSync(symlinkTarget, targetAbsolutePath);
563
568
  return;
564
569
  }
565
570
  if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
566
- symlinkSync(sourceAbsolutePath, targetAbsolutePath);
571
+ symlinkSync(symlinkTarget, targetAbsolutePath);
567
572
  return;
568
573
  }
569
574
  const stats = lstatSync2(targetAbsolutePath);
@@ -571,7 +576,7 @@ function applyAction(targetDir, action) {
571
576
  throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
572
577
  }
573
578
  unlinkSync(targetAbsolutePath);
574
- symlinkSync(sourceAbsolutePath, targetAbsolutePath);
579
+ symlinkSync(symlinkTarget, targetAbsolutePath);
575
580
  }
576
581
  function pathExistsEvenIfDanglingSymlink2(path) {
577
582
  try {
@@ -601,20 +606,19 @@ function checkInstalledAssets(options) {
601
606
  const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
602
607
  const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
603
608
  const issues = [];
609
+ const strictRegistry = options.strictRegistry ?? false;
604
610
  for (const entry of lockfile.assets ?? []) {
605
611
  const asset = registryById.get(entry.id);
606
612
  const targetAbsolutePath = join8(options.targetDir, entry.targetPath);
607
- const sourceAbsolutePath = join8(options.agentAssetsDir, entry.sourcePath);
608
- if (!asset) {
613
+ if (!asset && strictRegistry) {
609
614
  issues.push({
610
615
  type: "unknown-asset",
611
616
  id: entry.id,
612
617
  targetPath: entry.targetPath,
613
618
  message: `Lockfile references unknown asset: ${entry.id}`
614
619
  });
615
- continue;
616
620
  }
617
- if (asset.kind === "skill" && asset.defaultScope === "user") {
621
+ if (asset?.kind === "skill" && asset.defaultScope === "user") {
618
622
  issues.push({
619
623
  type: "user-scoped-asset-in-project-lock",
620
624
  id: entry.id,
@@ -622,13 +626,15 @@ function checkInstalledAssets(options) {
622
626
  message: `User-scoped skill is still locked into this project; move it to the user skill roots: ${entry.id}`
623
627
  });
624
628
  }
625
- const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
626
- if (hostFolderIssue) {
627
- issues.push(hostFolderIssue);
628
- }
629
- const placementDriftIssue = checkRegistryPlacement(lockfile, asset, entry.targetPath);
630
- if (placementDriftIssue) {
631
- issues.push(placementDriftIssue);
629
+ if (asset) {
630
+ const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
631
+ if (hostFolderIssue) {
632
+ issues.push(hostFolderIssue);
633
+ }
634
+ const placementDriftIssue = checkRegistryPlacement(lockfile, asset, entry.targetPath);
635
+ if (placementDriftIssue) {
636
+ issues.push(placementDriftIssue);
637
+ }
632
638
  }
633
639
  if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
634
640
  issues.push({
@@ -658,6 +664,18 @@ function checkInstalledAssets(options) {
658
664
  });
659
665
  continue;
660
666
  }
667
+ const currentTargetHash = hashAssetPathContent(targetAbsolutePath);
668
+ const targetHashMatchesLock = currentTargetHash === entry.contentHash;
669
+ if (!targetHashMatchesLock) {
670
+ issues.push({
671
+ type: "hash-drift",
672
+ id: entry.id,
673
+ targetPath: entry.targetPath,
674
+ message: `Managed asset hash drifted: ${entry.id}`
675
+ });
676
+ }
677
+ if (!asset || !strictRegistry) continue;
678
+ const sourceAbsolutePath = join8(options.agentAssetsDir, asset.sourcePath);
661
679
  if (!existsSync8(sourceAbsolutePath)) {
662
680
  issues.push({
663
681
  type: "missing-source",
@@ -667,8 +685,8 @@ function checkInstalledAssets(options) {
667
685
  });
668
686
  continue;
669
687
  }
670
- const currentHash = hashAgentAssetContent(asset, options.agentAssetsDir);
671
- if (currentHash !== entry.contentHash) {
688
+ const currentSourceHash = hashAgentAssetContent(asset, options.agentAssetsDir);
689
+ if (targetHashMatchesLock && currentSourceHash !== entry.contentHash) {
672
690
  issues.push({
673
691
  type: "hash-drift",
674
692
  id: entry.id,
@@ -1145,7 +1163,8 @@ function runAssetsCheck(args) {
1145
1163
  const result = checkInstalledAssets({
1146
1164
  targetDir: options.value.targetDir,
1147
1165
  agentAssetsDir: loaded.agentAssetsDir,
1148
- registry: loaded.registry
1166
+ registry: loaded.registry,
1167
+ strictRegistry: options.value.strictRegistry
1149
1168
  });
1150
1169
  if (options.value.json) {
1151
1170
  console.log(JSON.stringify(result, null, 2));
@@ -1300,6 +1319,8 @@ function parseTargetJsonOptions(args) {
1300
1319
  if (!targetDir) return { ok: false, error: "Expected --target <path>" };
1301
1320
  options.targetDir = targetDir;
1302
1321
  index += 1;
1322
+ } else if (arg === "--strict-registry") {
1323
+ options.strictRegistry = true;
1303
1324
  } else if (arg === "--json") {
1304
1325
  options.json = true;
1305
1326
  } else {
@@ -1717,7 +1738,7 @@ function bulletList(values) {
1717
1738
  // src/lens/scan.ts
1718
1739
  import { spawnSync as spawnSync3 } from "node:child_process";
1719
1740
  import { existsSync as existsSync13, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1720
- import { join as join13, relative as relative4 } from "node:path";
1741
+ import { join as join13, relative as relative5 } from "node:path";
1721
1742
  var ignoredDirectories = /* @__PURE__ */ new Set([
1722
1743
  ".git",
1723
1744
  ".next",
@@ -1792,7 +1813,7 @@ function collectFiles2(rootDir, currentDir, files) {
1792
1813
  if (ignoredDirectories.has(entry.name)) continue;
1793
1814
  collectFiles2(rootDir, join13(currentDir, entry.name), files);
1794
1815
  } else if (entry.isFile()) {
1795
- files.push(toUnixPath4(relative4(rootDir, join13(currentDir, entry.name))));
1816
+ files.push(toUnixPath4(relative5(rootDir, join13(currentDir, entry.name))));
1796
1817
  }
1797
1818
  }
1798
1819
  }
@@ -1886,6 +1907,10 @@ function printUsage2() {
1886
1907
  console.error("Usage: pro-gov lens report --target <path> --out <path>");
1887
1908
  }
1888
1909
 
1910
+ // src/commands/portfolio.ts
1911
+ import { existsSync as existsSync15 } from "node:fs";
1912
+ import { join as join14 } from "node:path";
1913
+
1889
1914
  // src/portfolio/manifest.ts
1890
1915
  import { existsSync as existsSync14, readFileSync as readFileSync9 } from "node:fs";
1891
1916
  function loadPortfolioManifest(configPath) {
@@ -1935,6 +1960,7 @@ function validatePortfolioManifest(value) {
1935
1960
  message: "Portfolio manifest portfolioId must be a non-empty string."
1936
1961
  });
1937
1962
  }
1963
+ validateAllowedFields(value, "root", ["schemaVersion", "portfolioId", "controlPlane", "executionEngine", "targets"], issues);
1938
1964
  validateEndpoint(value.controlPlane, "controlPlane", issues);
1939
1965
  validateEndpoint(value.executionEngine, "executionEngine", issues);
1940
1966
  if (!Array.isArray(value.targets)) {
@@ -1956,6 +1982,7 @@ function validatePortfolioManifest(value) {
1956
1982
  continue;
1957
1983
  }
1958
1984
  validateEndpoint(target, "targets", issues);
1985
+ validateAllowedFields(target, "target", ["id", "path", "profile", "assetBundles"], issues);
1959
1986
  if (typeof target.id === "string") {
1960
1987
  if (seenTargetIds.has(target.id)) {
1961
1988
  issues.push({
@@ -1966,14 +1993,41 @@ function validatePortfolioManifest(value) {
1966
1993
  }
1967
1994
  seenTargetIds.add(target.id);
1968
1995
  }
1996
+ if (target.profile !== void 0 && (typeof target.profile !== "string" || !isValidProfile(target.profile))) {
1997
+ issues.push({
1998
+ type: "invalid-field",
1999
+ id: typeof target.id === "string" ? target.id : void 0,
2000
+ field: "profile",
2001
+ message: "Portfolio target profile must be engineering-runtime or doc-only."
2002
+ });
2003
+ }
1969
2004
  validateOptionalStringArray(target.assetBundles, target.id, "assetBundles", issues);
1970
- validateOptionalStringArray(target.sharedRules, target.id, "sharedRules", issues);
2005
+ if ("sharedRules" in target) {
2006
+ issues.push({
2007
+ type: "invalid-field",
2008
+ id: typeof target.id === "string" ? target.id : void 0,
2009
+ field: "sharedRules",
2010
+ message: "Portfolio target sharedRules is not managed yet; remove it until plan/check supports it."
2011
+ });
2012
+ }
1971
2013
  }
1972
2014
  return issues;
1973
2015
  }
1974
2016
  function getDefaultPortfolioTargets(manifest) {
1975
2017
  return manifest?.targets ?? [];
1976
2018
  }
2019
+ function validateAllowedFields(value, location, allowedFields, issues) {
2020
+ const allowed = new Set(allowedFields);
2021
+ for (const field of Object.keys(value)) {
2022
+ if (allowed.has(field)) continue;
2023
+ issues.push({
2024
+ type: "invalid-field",
2025
+ id: typeof value.id === "string" ? value.id : void 0,
2026
+ field,
2027
+ message: `Unknown portfolio ${location} field: ${field}`
2028
+ });
2029
+ }
2030
+ }
1977
2031
  function validateEndpoint(value, field, issues) {
1978
2032
  if (value === void 0) return;
1979
2033
  if (!isRecord(value)) {
@@ -2029,6 +2083,7 @@ function runPortfolio(args) {
2029
2083
  const [subcommand2, ...rest] = args;
2030
2084
  if (subcommand2 === "check") return runPortfolioCheck(rest);
2031
2085
  if (subcommand2 === "plan") return runPortfolioPlan(rest);
2086
+ if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
2032
2087
  printUsage3();
2033
2088
  return 1;
2034
2089
  }
@@ -2085,7 +2140,9 @@ function runPortfolioPlan(args) {
2085
2140
  console.error(`Unknown portfolio target: ${options.value.targetId}`);
2086
2141
  return 1;
2087
2142
  }
2088
- const loadedAssets = loadAgentAssetRegistry();
2143
+ const loadedAssets = loadAgentAssetRegistry({
2144
+ agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
2145
+ });
2089
2146
  if (loadedAssets.issues.length > 0) {
2090
2147
  for (const issue of loadedAssets.issues) {
2091
2148
  console.error(`${issue.type}: ${issue.message}`);
@@ -2136,6 +2193,106 @@ function runPortfolioPlan(args) {
2136
2193
  return 1;
2137
2194
  }
2138
2195
  }
2196
+ function runPortfolioAssetsCheck(args) {
2197
+ const options = parsePortfolioOptions(args);
2198
+ if (!options.ok) {
2199
+ console.error(options.error);
2200
+ printUsage3();
2201
+ return 1;
2202
+ }
2203
+ const loaded = loadPortfolioManifest(options.value.configPath);
2204
+ if (loaded.issues.length > 0 || !loaded.manifest) {
2205
+ if (options.value.json) {
2206
+ console.log(
2207
+ JSON.stringify(
2208
+ {
2209
+ ok: false,
2210
+ configPath: loaded.configPath,
2211
+ issues: loaded.issues,
2212
+ targets: []
2213
+ },
2214
+ null,
2215
+ 2
2216
+ )
2217
+ );
2218
+ } else {
2219
+ for (const issue of loaded.issues) {
2220
+ console.error(`${issue.type}: ${issue.message}`);
2221
+ }
2222
+ }
2223
+ return 1;
2224
+ }
2225
+ const targets = getDefaultPortfolioTargets(loaded.manifest).filter(
2226
+ (target) => !options.value.targetId || options.value.targetId === "all" || target.id === options.value.targetId
2227
+ );
2228
+ if (targets.length === 0) {
2229
+ console.error(`Unknown portfolio target: ${options.value.targetId}`);
2230
+ return 1;
2231
+ }
2232
+ const loadedAssets = loadAgentAssetRegistry({
2233
+ agentAssetsDir: findPortfolioAgentAssetsDir(loaded.manifest)
2234
+ });
2235
+ if (loadedAssets.issues.length > 0) {
2236
+ if (options.value.json) {
2237
+ console.log(
2238
+ JSON.stringify(
2239
+ {
2240
+ ok: false,
2241
+ configPath: loaded.configPath,
2242
+ portfolioId: loaded.manifest.portfolioId,
2243
+ registryIssues: loadedAssets.issues,
2244
+ targets: []
2245
+ },
2246
+ null,
2247
+ 2
2248
+ )
2249
+ );
2250
+ } else {
2251
+ for (const issue of loadedAssets.issues) {
2252
+ console.error(`${issue.type}: ${issue.message}`);
2253
+ }
2254
+ }
2255
+ return 1;
2256
+ }
2257
+ const targetResults = targets.map((target) => {
2258
+ const result = checkInstalledAssets({
2259
+ targetDir: target.path,
2260
+ agentAssetsDir: loadedAssets.agentAssetsDir,
2261
+ registry: loadedAssets.registry,
2262
+ strictRegistry: true
2263
+ });
2264
+ return {
2265
+ id: target.id,
2266
+ path: target.path,
2267
+ issues: result.issues
2268
+ };
2269
+ });
2270
+ const ok = targetResults.every((target) => target.issues.length === 0);
2271
+ if (options.value.json) {
2272
+ console.log(
2273
+ JSON.stringify(
2274
+ {
2275
+ ok,
2276
+ configPath: loaded.configPath,
2277
+ portfolioId: loaded.manifest.portfolioId,
2278
+ agentAssetsDir: loadedAssets.agentAssetsDir,
2279
+ targets: targetResults
2280
+ },
2281
+ null,
2282
+ 2
2283
+ )
2284
+ );
2285
+ } else if (ok) {
2286
+ console.log(`portfolio assets check passed (${targetResults.length} targets)`);
2287
+ } else {
2288
+ for (const target of targetResults) {
2289
+ for (const issue of target.issues) {
2290
+ console.log(`${target.id} ${issue.type}: ${issue.message}`);
2291
+ }
2292
+ }
2293
+ }
2294
+ return ok ? 0 : 1;
2295
+ }
2139
2296
  function parsePortfolioOptions(args) {
2140
2297
  const options = {
2141
2298
  configPath: "",
@@ -2171,15 +2328,20 @@ function parsePortfolioOptions(args) {
2171
2328
  function isHost2(value) {
2172
2329
  return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
2173
2330
  }
2331
+ function findPortfolioAgentAssetsDir(manifest) {
2332
+ const agentAssetsDir = manifest?.executionEngine?.path ? join14(manifest.executionEngine.path, "agent-assets") : void 0;
2333
+ return agentAssetsDir && existsSync15(join14(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
2334
+ }
2174
2335
  function printUsage3() {
2175
2336
  console.error("Usage:");
2176
2337
  console.error(" pro-gov portfolio check --config <path> [--json]");
2177
2338
  console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
2339
+ console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
2178
2340
  }
2179
2341
 
2180
2342
  // src/commands/sync.ts
2181
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
2182
- import { join as join14 } from "node:path";
2343
+ import { existsSync as existsSync16, readFileSync as readFileSync10 } from "node:fs";
2344
+ import { join as join15 } from "node:path";
2183
2345
  function runSync(args) {
2184
2346
  if (!args.includes("--check")) {
2185
2347
  console.error("pro-gov sync requires --check in this first read-only release.");
@@ -2188,8 +2350,8 @@ function runSync(args) {
2188
2350
  let differences = 0;
2189
2351
  console.log("pro-gov sync check");
2190
2352
  for (const file of planStarterFiles()) {
2191
- const targetPath = join14(process.cwd(), file.targetPath);
2192
- if (!existsSync15(targetPath)) {
2353
+ const targetPath = join15(process.cwd(), file.targetPath);
2354
+ if (!existsSync16(targetPath)) {
2193
2355
  console.log(`missing: ${file.targetPath}`);
2194
2356
  differences += 1;
2195
2357
  continue;
@@ -2216,11 +2378,12 @@ var COMMANDS = [
2216
2378
  "assets recommend [--target <path>] [--json]",
2217
2379
  "assets plan --bundle <bundle-id> [--target <path>] [--json]",
2218
2380
  "assets apply --plan <path>",
2219
- "assets check [--target <path>] [--json]",
2381
+ "assets check [--target <path>] [--strict-registry] [--json]",
2220
2382
  "assets public-check [--public-root <path>] [--private-root <path>] [--json]",
2221
2383
  "assets npx add|update ... --plan",
2222
2384
  "portfolio check --config <path> [--json]",
2223
2385
  "portfolio plan --config <path> [--target <id|all>] [--json]",
2386
+ "portfolio assets-check --config <path> [--target <id|all>] [--json]",
2224
2387
  "lens scan [--target <path>] [--json]",
2225
2388
  "lens inspect [--target <path>] [--format text|json]",
2226
2389
  "lens report --target <path> --out <path>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pieai/pro-gov",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Project-level distribution kit for Project Governance System.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@pieai/doc-gov": "^0.3.6"
38
+ "@pieai/doc-gov": "^0.3.7"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "24.13.2",