@hobocode/thought-layer 0.7.0 → 0.8.5

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/core/progress.ts CHANGED
@@ -21,10 +21,10 @@ export const PROGRESS_FORMAT = 2;
21
21
 
22
22
  export const KNOWN_STATE_KEYS = [
23
23
  "version", "answers", "feedback", "bizModel", "grill",
24
- "assets", "research", "swot", "prd", "naming", "brand", "kit",
24
+ "assets", "research", "swot", "prd", "naming", "brand", "governance", "kit",
25
25
  ] as const;
26
26
 
27
- export type ArtifactKey = "bizModel" | "grill" | "assets" | "research" | "swot" | "prd" | "naming" | "brand";
27
+ export type ArtifactKey = "bizModel" | "grill" | "assets" | "research" | "swot" | "prd" | "naming" | "brand" | "governance";
28
28
 
29
29
  export interface Writer { kind: "web" | "kit"; version?: string; ts: number; }
30
30
 
@@ -81,6 +81,7 @@ export interface ProgressState {
81
81
  prd: unknown;
82
82
  naming: unknown;
83
83
  brand: unknown;
84
+ governance: unknown;
84
85
  kit: KitNamespace | null;
85
86
  [extra: string]: unknown;
86
87
  }
@@ -99,7 +100,7 @@ export interface ProgressPayload {
99
100
  export function emptyState(): ProgressState {
100
101
  return {
101
102
  version: 2, answers: {}, feedback: {}, bizModel: null, grill: null,
102
- assets: null, research: null, swot: null, prd: null, naming: null, brand: null, kit: null,
103
+ assets: null, research: null, swot: null, prd: null, naming: null, brand: null, governance: null, kit: null,
103
104
  };
104
105
  }
105
106
 
@@ -132,6 +133,7 @@ export function parseProgress(text: string): ProgressPayload {
132
133
  prd: s["prd"] ?? null,
133
134
  naming: s["naming"] ?? null,
134
135
  brand: s["brand"] ?? null,
136
+ governance: s["governance"] ?? null,
135
137
  kit: (s["kit"] as KitNamespace | undefined) ?? null,
136
138
  };
137
139
  return {
@@ -149,7 +151,7 @@ export function parseProgress(text: string): ProgressPayload {
149
151
  export function buildProgress(state: Partial<ProgressState>, writer: Writer, exportedAt: string): ProgressPayload {
150
152
  const s = (state || {}) as Record<string, unknown>;
151
153
  const {
152
- answers, feedback, bizModel, grill, assets, research, swot, prd, naming, brand, kit,
154
+ answers, feedback, bizModel, grill, assets, research, swot, prd, naming, brand, governance, kit,
153
155
  version: _v, exportedAt: _ea, formatNewer: _fn, ...rest
154
156
  } = s;
155
157
  return {
@@ -170,6 +172,7 @@ export function buildProgress(state: Partial<ProgressState>, writer: Writer, exp
170
172
  prd: prd ?? null,
171
173
  naming: naming ?? null,
172
174
  brand: brand ?? null,
175
+ governance: governance ?? null,
173
176
  kit: (kit as KitNamespace | undefined) ?? null,
174
177
  ...rest,
175
178
  },
@@ -299,7 +302,7 @@ export function summarizeState(state: ProgressState): StateSummary {
299
302
  if (s === "green" || s === "yellow" || s === "red") byStatus[s]++;
300
303
  else byStatus.ungraded++;
301
304
  }
302
- const artifacts = (["bizModel", "grill", "assets", "research", "swot", "prd", "naming", "brand"] as const)
305
+ const artifacts = (["bizModel", "grill", "assets", "research", "swot", "prd", "naming", "brand", "governance"] as const)
303
306
  .filter((k) => state[k] != null);
304
307
  const kit = (state.kit && typeof state.kit === "object") ? state.kit : null;
305
308
  return {
package/core/state-ops.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  type ArtifactKey, type PersonaInput, type EndState, type KitCursor,
13
13
  } from "./progress.ts";
14
14
 
15
- export const ARTIFACT_KEYS: ArtifactKey[] = ["bizModel", "grill", "assets", "research", "swot", "prd", "naming", "brand"];
15
+ export const ARTIFACT_KEYS: ArtifactKey[] = ["bizModel", "grill", "assets", "research", "swot", "prd", "naming", "brand", "governance"];
16
16
  const END_STATES: EndState[] = ["open", "pass", "setAside"];
17
17
 
18
18
  export interface StateOp {
package/core/sync-io.ts CHANGED
@@ -60,7 +60,7 @@ export function ghAuthed(): boolean {
60
60
  function syncConfigPath(): string {
61
61
  return process.env["THOUGHT_LAYER_SYNC_CONFIG"] || join(homedir(), ".thought-layer", "sync.json");
62
62
  }
63
- function loadConfig(): SyncConfig {
63
+ export function loadConfig(): SyncConfig {
64
64
  const p = syncConfigPath();
65
65
  if (!existsSync(p)) return emptySyncConfig();
66
66
  try { return parseSyncConfig(readFileSync(p, "utf8")); }
@@ -74,12 +74,14 @@ function saveConfig(cfg: SyncConfig): void {
74
74
 
75
75
  // ---- git helpers -------------------------------------------------------------
76
76
 
77
- interface Run { status: number; out: string; err: string; }
78
- function git(dir: string | null, args: string[], timeout = 120000): Run {
77
+ export interface Run { status: number; out: string; err: string; }
78
+ // Exported so the artifacts delivery layer (artifacts-io.ts) reuses the exact
79
+ // same git shell-out plumbing rather than re-implementing spawnSync.
80
+ export function git(dir: string | null, args: string[], timeout = 120000): Run {
79
81
  const r = spawnSync("git", dir ? ["-C", dir, ...args] : args, { encoding: "utf8", timeout });
80
82
  return { status: r.status ?? 1, out: r.stdout || "", err: r.stderr || "" };
81
83
  }
82
- function isGitRepo(dir: string): boolean {
84
+ export function isGitRepo(dir: string): boolean {
83
85
  return existsSync(join(dir, ".git")) && git(dir, ["rev-parse", "--is-inside-work-tree"]).status === 0;
84
86
  }
85
87
  function dirNonEmpty(dir: string): boolean {
@@ -91,7 +93,7 @@ const absDir = (d: string, cwd = process.cwd()): string => (isAbsolute(d) ? d :
91
93
  // ---- the clone scaffolding files written on init -----------------------------
92
94
 
93
95
  const GITATTRIBUTES = `# The kit reconciles session JSON itself; never let git textually merge it\n# (which would corrupt the envelope). -merge keeps our copy in the working tree\n# on a conflict; the kit then rebuilds the merged result from the clean blobs.\n.thought-layer/*.json -merge\n`;
94
- const GITIGNORE = `# A Thought Layer sessions repo holds session state only. Built product\n# artifacts and secrets never sync here.\nbuild.json\ndeploy.json\n*.local\n.env\n.env.*\n!.env.example\ndist/\n.netlify/\nnode_modules/\n`;
96
+ const GITIGNORE = `# A Thought Layer sessions repo holds session state only. Built product\n# artifacts and secrets never sync here.\nbuild.json\ndeploy.json\n*.local\n.env\n.env.*\n!.env.example\ndist/\n.netlify/\nnode_modules/\n# Delivered artifacts (tl artifacts) ARE intentionally tracked, even though the\n# same filenames are ignored elsewhere in the tree.\n!artifacts/\n!artifacts/**\n`;
95
97
  const README = `# Thought Layer sessions\n\nThis private repo is the home for Thought Layer session files. Each session is one\nfile under \`.thought-layer/<name>.json\` (the portable validation and design state).\nUse the kit to work with them:\n\n tl sync open --name <session> pull and resume a session\n tl sync save --name <session> snapshot the current state, commit, and push\n tl sync list list the sessions in this repo\n\nCollaboration is handled by GitHub: add a collaborator to this repo in its GitHub\nsettings, and they can clone it and run the kit against the same sessions.\nThe kit reconciles concurrent edits itself (newest wins per field, conflicts are\nreported), so git never has to merge the JSON by hand.\n`;
96
98
 
97
99
  function writeCloneScaffold(cloneDir: string): void {
@@ -136,7 +138,8 @@ export async function runSync(opts: SyncRunOptions, ctx: { ts: number; exportedA
136
138
  }
137
139
 
138
140
  // Resolve the workspace + clone dir an op targets (everything except init).
139
- function resolveWorkspace(opts: SyncRunOptions, cfg: SyncConfig): { cloneDir: string; ws: SyncWorkspace | null } {
141
+ // Exported so artifacts-io.ts resolves the same sessions workspace.
142
+ export function resolveWorkspace(opts: SyncRunOptions, cfg: SyncConfig): { cloneDir: string; ws: SyncWorkspace | null } {
140
143
  if (opts.dir && opts.dir.trim()) return { cloneDir: absDir(opts.dir), ws: null };
141
144
  const env = process.env["THOUGHT_LAYER_SESSIONS_DIR"];
142
145
  if (env && env.trim()) return { cloneDir: absDir(env), ws: null };