@kitsy/coop-core 1.0.0 → 2.1.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/dist/index.cjs CHANGED
@@ -72,6 +72,11 @@ __export(index_exports, {
72
72
  compute_readiness_with_corrections: () => compute_readiness_with_corrections,
73
73
  compute_score: () => compute_score,
74
74
  compute_velocity: () => compute_velocity,
75
+ coop_project_config_path: () => coop_project_config_path,
76
+ coop_project_root: () => coop_project_root,
77
+ coop_projects_dir: () => coop_projects_dir,
78
+ coop_workspace_config_path: () => coop_workspace_config_path,
79
+ coop_workspace_dir: () => coop_workspace_dir,
75
80
  createItem: () => createItem,
76
81
  create_seeded_rng: () => create_seeded_rng,
77
82
  critical_path_weight: () => critical_path_weight,
@@ -83,6 +88,7 @@ __export(index_exports, {
83
88
  effective_weekly_hours: () => effective_weekly_hours,
84
89
  effort_or_default: () => effort_or_default,
85
90
  ensureCoopLayout: () => ensureCoopLayout,
91
+ ensure_workspace_layout: () => ensure_workspace_layout,
86
92
  executor_fit_weight: () => executor_fit_weight,
87
93
  external_dependencies_for_task: () => external_dependencies_for_task,
88
94
  extract_subgraph: () => extract_subgraph,
@@ -91,7 +97,11 @@ __export(index_exports, {
91
97
  getItemById: () => getItemById,
92
98
  get_remaining_tokens: () => get_remaining_tokens,
93
99
  get_user_role: () => get_user_role,
100
+ has_legacy_project_layout: () => has_legacy_project_layout,
101
+ has_v2_projects_layout: () => has_v2_projects_layout,
94
102
  is_external_dependency: () => is_external_dependency,
103
+ is_project_initialized: () => is_project_initialized,
104
+ list_projects: () => list_projects,
95
105
  loadState: () => loadState,
96
106
  load_auth_config: () => load_auth_config,
97
107
  load_completed_runs: () => load_completed_runs,
@@ -116,9 +126,14 @@ __export(index_exports, {
116
126
  pert_stddev: () => pert_stddev,
117
127
  priority_weight: () => priority_weight,
118
128
  queryItems: () => queryItems,
129
+ read_project_config: () => read_project_config,
119
130
  read_schema_version: () => read_schema_version,
131
+ read_workspace_config: () => read_workspace_config,
120
132
  renderAgentPrompt: () => renderAgentPrompt,
133
+ repo_default_project_id: () => repo_default_project_id,
134
+ repo_default_project_name: () => repo_default_project_name,
121
135
  resolve_external_dependencies: () => resolve_external_dependencies,
136
+ resolve_project: () => resolve_project,
122
137
  risk_penalty: () => risk_penalty,
123
138
  run_hook: () => run_hook,
124
139
  run_monte_carlo_chunk: () => run_monte_carlo_chunk,
@@ -147,7 +162,8 @@ __export(index_exports, {
147
162
  validate_transition: () => validate_transition,
148
163
  writeTask: () => writeTask,
149
164
  writeYamlFile: () => writeYamlFile,
150
- write_schema_version: () => write_schema_version
165
+ write_schema_version: () => write_schema_version,
166
+ write_workspace_config: () => write_workspace_config
151
167
  });
152
168
  module.exports = __toCommonJS(index_exports);
153
169
 
@@ -2160,6 +2176,9 @@ var TASK_FIELD_ORDER = [
2160
2176
  "depends_on",
2161
2177
  "tags",
2162
2178
  "delivery",
2179
+ "acceptance",
2180
+ "tests_required",
2181
+ "origin",
2163
2182
  "complexity",
2164
2183
  "determinism",
2165
2184
  "estimate",
@@ -4694,6 +4713,7 @@ var import_node_path8 = __toESM(require("path"), 1);
4694
4713
  var ID_PATTERN = /^[A-Z0-9]+(?:-[A-Z0-9]+)+$/;
4695
4714
  var ALIAS_PATTERN = /^[A-Z0-9]+(?:[.-][A-Z0-9]+)*$/;
4696
4715
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
4716
+ var SHA256_RE = /^[a-f0-9]{64}$/i;
4697
4717
  function error4(field, rule, message) {
4698
4718
  return { level: "error", field, rule, message };
4699
4719
  }
@@ -4707,6 +4727,23 @@ function isIsoDate(value) {
4707
4727
  }
4708
4728
  return date.toISOString().slice(0, 10) === value;
4709
4729
  }
4730
+ function validateStringArrayField(errors, field, value, options = {}) {
4731
+ if (value === void 0) {
4732
+ return;
4733
+ }
4734
+ if (!Array.isArray(value)) {
4735
+ errors.push(error4(field, `struct.${field}_array`, `Field '${field}' must be an array of strings.`));
4736
+ return;
4737
+ }
4738
+ for (const entry of value) {
4739
+ if (typeof entry !== "string" || entry.trim().length < (options.minLength ?? 1)) {
4740
+ errors.push(
4741
+ error4(field, `struct.${field}_string`, `Field '${field}' entries must be non-empty strings.`)
4742
+ );
4743
+ return;
4744
+ }
4745
+ }
4746
+ }
4710
4747
  function validateStructural(task, context = {}) {
4711
4748
  const errors = [];
4712
4749
  const required = ["id", "title", "type", "status", "created", "updated"];
@@ -4771,6 +4808,27 @@ function validateStructural(task, context = {}) {
4771
4808
  }
4772
4809
  }
4773
4810
  }
4811
+ validateStringArrayField(errors, "acceptance", task.acceptance);
4812
+ validateStringArrayField(errors, "tests_required", task.tests_required);
4813
+ if (task.origin !== void 0) {
4814
+ if (!task.origin || typeof task.origin !== "object" || Array.isArray(task.origin)) {
4815
+ errors.push(error4("origin", "struct.origin_object", "Field 'origin' must be an object."));
4816
+ } else {
4817
+ validateStringArrayField(errors, "origin.authority_refs", task.origin.authority_refs);
4818
+ validateStringArrayField(errors, "origin.derived_refs", task.origin.derived_refs);
4819
+ validateStringArrayField(errors, "origin.promoted_from", task.origin.promoted_from);
4820
+ validateStringArrayField(errors, "origin.promoted_to", task.origin.promoted_to);
4821
+ if (task.origin.snapshot_sha256 !== void 0 && (typeof task.origin.snapshot_sha256 !== "string" || !SHA256_RE.test(task.origin.snapshot_sha256))) {
4822
+ errors.push(
4823
+ error4(
4824
+ "origin.snapshot_sha256",
4825
+ "struct.origin_snapshot_sha256",
4826
+ "Field 'origin.snapshot_sha256' must be a 64-character SHA-256 hex string."
4827
+ )
4828
+ );
4829
+ }
4830
+ }
4831
+ }
4774
4832
  return errors;
4775
4833
  }
4776
4834
 
@@ -4858,9 +4916,131 @@ function validate(task, context = {}) {
4858
4916
  };
4859
4917
  }
4860
4918
 
4861
- // src/core.ts
4919
+ // src/workspace.ts
4862
4920
  var import_node_fs14 = __toESM(require("fs"), 1);
4863
4921
  var import_node_path9 = __toESM(require("path"), 1);
4922
+ var COOP_DIR_NAME = ".coop";
4923
+ function sanitizeProjectId(value, fallback) {
4924
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
4925
+ return normalized || fallback;
4926
+ }
4927
+ function coop_workspace_dir(repoRoot) {
4928
+ return import_node_path9.default.join(import_node_path9.default.resolve(repoRoot), COOP_DIR_NAME);
4929
+ }
4930
+ function coop_projects_dir(repoRoot) {
4931
+ return import_node_path9.default.join(coop_workspace_dir(repoRoot), "projects");
4932
+ }
4933
+ function coop_workspace_config_path(repoRoot) {
4934
+ return import_node_path9.default.join(coop_workspace_dir(repoRoot), "config.yml");
4935
+ }
4936
+ function coop_project_root(repoRoot, projectId) {
4937
+ return import_node_path9.default.join(coop_projects_dir(repoRoot), projectId);
4938
+ }
4939
+ function coop_project_config_path(projectRoot) {
4940
+ return import_node_path9.default.join(projectRoot, "config.yml");
4941
+ }
4942
+ function repo_default_project_id(repoRoot) {
4943
+ return sanitizeProjectId(import_node_path9.default.basename(import_node_path9.default.resolve(repoRoot)), "workspace");
4944
+ }
4945
+ function repo_default_project_name(repoRoot) {
4946
+ const base = import_node_path9.default.basename(import_node_path9.default.resolve(repoRoot)).trim();
4947
+ return base || "COOP Workspace";
4948
+ }
4949
+ function has_v2_projects_layout(repoRoot) {
4950
+ return import_node_fs14.default.existsSync(coop_projects_dir(repoRoot));
4951
+ }
4952
+ function has_legacy_project_layout(repoRoot) {
4953
+ const workspaceDir = coop_workspace_dir(repoRoot);
4954
+ return import_node_fs14.default.existsSync(workspaceDir) && import_node_fs14.default.existsSync(import_node_path9.default.join(workspaceDir, "config.yml")) && !import_node_fs14.default.existsSync(coop_projects_dir(repoRoot));
4955
+ }
4956
+ function read_workspace_config(repoRoot) {
4957
+ const configPath = coop_workspace_config_path(repoRoot);
4958
+ if (!import_node_fs14.default.existsSync(configPath) || has_legacy_project_layout(repoRoot)) {
4959
+ return { version: 2 };
4960
+ }
4961
+ return parseYamlFile(configPath);
4962
+ }
4963
+ function write_workspace_config(repoRoot, config) {
4964
+ import_node_fs14.default.mkdirSync(coop_workspace_dir(repoRoot), { recursive: true });
4965
+ writeYamlFile(coop_workspace_config_path(repoRoot), {
4966
+ version: config.version ?? 2,
4967
+ ...config.current_project ? { current_project: config.current_project } : {}
4968
+ });
4969
+ }
4970
+ function read_project_config(projectRoot) {
4971
+ return parseYamlFile(coop_project_config_path(projectRoot));
4972
+ }
4973
+ function project_ref_from_config(repoRoot, projectRoot, layout) {
4974
+ const config = read_project_config(projectRoot);
4975
+ const repoName = repo_default_project_name(repoRoot);
4976
+ const fallbackId = repo_default_project_id(repoRoot);
4977
+ return {
4978
+ id: sanitizeProjectId(config.project?.id ?? fallbackId, fallbackId),
4979
+ name: config.project?.name?.trim() || repoName,
4980
+ aliases: Array.isArray(config.project?.aliases) ? config.project.aliases.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [],
4981
+ root: projectRoot,
4982
+ repo_root: import_node_path9.default.resolve(repoRoot),
4983
+ layout
4984
+ };
4985
+ }
4986
+ function list_projects(repoRoot) {
4987
+ if (has_v2_projects_layout(repoRoot)) {
4988
+ const projectsDir = coop_projects_dir(repoRoot);
4989
+ return import_node_fs14.default.readdirSync(projectsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => import_node_path9.default.join(projectsDir, entry.name)).filter((projectRoot) => import_node_fs14.default.existsSync(coop_project_config_path(projectRoot))).map((projectRoot) => project_ref_from_config(repoRoot, projectRoot, "v2")).sort((a, b) => a.id.localeCompare(b.id));
4990
+ }
4991
+ if (has_legacy_project_layout(repoRoot)) {
4992
+ return [project_ref_from_config(repoRoot, coop_workspace_dir(repoRoot), "legacy")];
4993
+ }
4994
+ return [];
4995
+ }
4996
+ function resolve_project(repoRoot, options = {}) {
4997
+ const projects = list_projects(repoRoot);
4998
+ const requested = options.project?.trim().toLowerCase();
4999
+ if (requested) {
5000
+ const match = projects.find(
5001
+ (project) => project.id.toLowerCase() === requested || project.name.toLowerCase() === requested || project.aliases.some((alias) => alias.toLowerCase() === requested)
5002
+ );
5003
+ if (!match) {
5004
+ throw new Error(`Project '${options.project}' not found.`);
5005
+ }
5006
+ return match;
5007
+ }
5008
+ if (projects.length === 1) {
5009
+ return projects[0];
5010
+ }
5011
+ const workspaceConfig = read_workspace_config(repoRoot);
5012
+ if (workspaceConfig.current_project) {
5013
+ const match = projects.find((project) => project.id === workspaceConfig.current_project);
5014
+ if (match) return match;
5015
+ }
5016
+ if (!options.require && projects.length === 0) {
5017
+ return {
5018
+ id: repo_default_project_id(repoRoot),
5019
+ name: repo_default_project_name(repoRoot),
5020
+ aliases: [],
5021
+ root: coop_project_root(repoRoot, repo_default_project_id(repoRoot)),
5022
+ repo_root: import_node_path9.default.resolve(repoRoot),
5023
+ layout: "v2"
5024
+ };
5025
+ }
5026
+ if (projects.length === 0) {
5027
+ throw new Error("No COOP project found. Run 'coop init'.");
5028
+ }
5029
+ throw new Error("Multiple COOP projects found. Pass --project <id> or run 'coop project use <id>'.");
5030
+ }
5031
+ function ensure_workspace_layout(repoRoot) {
5032
+ const workspaceDir = coop_workspace_dir(repoRoot);
5033
+ import_node_fs14.default.mkdirSync(workspaceDir, { recursive: true });
5034
+ import_node_fs14.default.mkdirSync(coop_projects_dir(repoRoot), { recursive: true });
5035
+ return workspaceDir;
5036
+ }
5037
+ function is_project_initialized(projectRoot) {
5038
+ return import_node_fs14.default.existsSync(coop_project_config_path(projectRoot));
5039
+ }
5040
+
5041
+ // src/core.ts
5042
+ var import_node_fs15 = __toESM(require("fs"), 1);
5043
+ var import_node_path10 = __toESM(require("path"), 1);
4864
5044
  var import_gray_matter = __toESM(require("gray-matter"), 1);
4865
5045
 
4866
5046
  // src/types.ts
@@ -4886,17 +5066,17 @@ function toIdKey(value) {
4886
5066
  return value.trim().toUpperCase();
4887
5067
  }
4888
5068
  function repoRootByPackage(cwd) {
4889
- let current = import_node_path9.default.resolve(cwd);
5069
+ let current = import_node_path10.default.resolve(cwd);
4890
5070
  let lastWorkspaceRoot = null;
4891
5071
  while (true) {
4892
- const packageJson = import_node_path9.default.join(current, "package.json");
4893
- const workspaceYaml = import_node_path9.default.join(current, "pnpm-workspace.yaml");
4894
- if (import_node_fs14.default.existsSync(packageJson) && import_node_fs14.default.existsSync(workspaceYaml)) {
5072
+ const packageJson = import_node_path10.default.join(current, "package.json");
5073
+ const workspaceYaml = import_node_path10.default.join(current, "pnpm-workspace.yaml");
5074
+ if (import_node_fs15.default.existsSync(packageJson) && import_node_fs15.default.existsSync(workspaceYaml)) {
4895
5075
  lastWorkspaceRoot = current;
4896
- const hasCoop = import_node_fs14.default.existsSync(import_node_path9.default.join(current, COOP_DIR, "config.yml"));
5076
+ const hasCoop = import_node_fs15.default.existsSync(import_node_path10.default.join(current, COOP_DIR, "config.yml"));
4897
5077
  if (hasCoop) return current;
4898
5078
  }
4899
- const parent = import_node_path9.default.dirname(current);
5079
+ const parent = import_node_path10.default.dirname(current);
4900
5080
  if (parent === current) return lastWorkspaceRoot;
4901
5081
  current = parent;
4902
5082
  }
@@ -4905,24 +5085,24 @@ function findRepoRoot(cwd = process.cwd()) {
4905
5085
  return repoRootByPackage(cwd);
4906
5086
  }
4907
5087
  function configPathFor(rootDir, workspaceDir) {
4908
- return import_node_path9.default.join(rootDir, workspaceDir, "config.yml");
5088
+ return import_node_path10.default.join(rootDir, workspaceDir, "config.yml");
4909
5089
  }
4910
5090
  function backlogPathFor(rootDir, workspaceDir) {
4911
- return import_node_path9.default.join(rootDir, workspaceDir, "backlog");
5091
+ return import_node_path10.default.join(rootDir, workspaceDir, "backlog");
4912
5092
  }
4913
5093
  function releasesPathFor(rootDir, workspaceDir) {
4914
- return import_node_path9.default.join(rootDir, workspaceDir, "releases");
5094
+ return import_node_path10.default.join(rootDir, workspaceDir, "releases");
4915
5095
  }
4916
5096
  function detectWorkspaceDir(rootDir) {
4917
- if (import_node_fs14.default.existsSync(configPathFor(rootDir, COOP_DIR))) return COOP_DIR;
4918
- if (import_node_fs14.default.existsSync(import_node_path9.default.join(rootDir, COOP_DIR))) return COOP_DIR;
5097
+ if (import_node_fs15.default.existsSync(configPathFor(rootDir, COOP_DIR))) return COOP_DIR;
5098
+ if (import_node_fs15.default.existsSync(import_node_path10.default.join(rootDir, COOP_DIR))) return COOP_DIR;
4919
5099
  return null;
4920
5100
  }
4921
5101
  function preferredWorkspaceDir(rootDir) {
4922
5102
  return detectWorkspaceDir(rootDir) ?? COOP_DIR;
4923
5103
  }
4924
5104
  function missingConfigError(rootDir) {
4925
- const coopConfig = import_node_path9.default.relative(rootDir, configPathFor(rootDir, COOP_DIR));
5105
+ const coopConfig = import_node_path10.default.relative(rootDir, configPathFor(rootDir, COOP_DIR));
4926
5106
  return new Error(`COOP config missing at ${coopConfig}. Run: coop init`);
4927
5107
  }
4928
5108
  function parseConfig(raw) {
@@ -4968,10 +5148,10 @@ function configToString(config) {
4968
5148
  return lines.join("\n");
4969
5149
  }
4970
5150
  function toPortablePath(value) {
4971
- return value.split(import_node_path9.default.sep).join("/");
5151
+ return value.split(import_node_path10.default.sep).join("/");
4972
5152
  }
4973
5153
  function ensureReleasesDir(rootDir, workspaceDir) {
4974
- import_node_fs14.default.mkdirSync(releasesPathFor(rootDir, workspaceDir), { recursive: true });
5154
+ import_node_fs15.default.mkdirSync(releasesPathFor(rootDir, workspaceDir), { recursive: true });
4975
5155
  }
4976
5156
  function releaseHeader(date) {
4977
5157
  return `## ${date}`;
@@ -4993,12 +5173,12 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
4993
5173
  ensureReleasesDir(rootDir, workspaceDir);
4994
5174
  const now = /* @__PURE__ */ new Date();
4995
5175
  const date = now.toISOString().slice(0, 10);
4996
- const releasePath = import_node_path9.default.join(releasesPathFor(rootDir, workspaceDir), `${date}.md`);
5176
+ const releasePath = import_node_path10.default.join(releasesPathFor(rootDir, workspaceDir), `${date}.md`);
4997
5177
  const heading = "# COOP Release Notes";
4998
5178
  const dayHeader = releaseHeader(date);
4999
5179
  const entry = releaseEntryLine(item, previousStatus, nextStatus);
5000
- if (!import_node_fs14.default.existsSync(releasePath)) {
5001
- import_node_fs14.default.writeFileSync(
5180
+ if (!import_node_fs15.default.existsSync(releasePath)) {
5181
+ import_node_fs15.default.writeFileSync(
5002
5182
  releasePath,
5003
5183
  [
5004
5184
  `${heading}
@@ -5011,10 +5191,10 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
5011
5191
  ].join("\n"),
5012
5192
  "utf8"
5013
5193
  );
5014
- return toPortablePath(import_node_path9.default.relative(rootDir, releasePath));
5194
+ return toPortablePath(import_node_path10.default.relative(rootDir, releasePath));
5015
5195
  }
5016
- const existing = import_node_fs14.default.readFileSync(releasePath, "utf8");
5017
- if (hasReleaseEntry(existing, item.id)) return toPortablePath(import_node_path9.default.relative(rootDir, releasePath));
5196
+ const existing = import_node_fs15.default.readFileSync(releasePath, "utf8");
5197
+ if (hasReleaseEntry(existing, item.id)) return toPortablePath(import_node_path10.default.relative(rootDir, releasePath));
5018
5198
  let nextContent = existing;
5019
5199
  if (!existing.includes(`## ${date}`)) {
5020
5200
  if (!nextContent.endsWith("\n")) nextContent += "\n";
@@ -5024,9 +5204,9 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
5024
5204
  if (!nextContent.endsWith("\n")) nextContent += "\n";
5025
5205
  nextContent += `${entry}
5026
5206
  `;
5027
- import_node_fs14.default.writeFileSync(releasePath, `${nextContent}
5207
+ import_node_fs15.default.writeFileSync(releasePath, `${nextContent}
5028
5208
  `, "utf8");
5029
- return toPortablePath(import_node_path9.default.relative(rootDir, releasePath));
5209
+ return toPortablePath(import_node_path10.default.relative(rootDir, releasePath));
5030
5210
  }
5031
5211
  function completeItem(rootDir, id) {
5032
5212
  const state = loadState(rootDir);
@@ -5104,28 +5284,28 @@ function validateAndNormalize(data, sourceFile) {
5104
5284
  };
5105
5285
  }
5106
5286
  function parseItem(filePath, rootDir) {
5107
- const raw = import_node_fs14.default.readFileSync(filePath, "utf8");
5287
+ const raw = import_node_fs15.default.readFileSync(filePath, "utf8");
5108
5288
  const parsed = (0, import_gray_matter.default)(raw);
5109
- const data = validateAndNormalize(parsed.data, import_node_path9.default.relative(rootDir, filePath));
5289
+ const data = validateAndNormalize(parsed.data, import_node_path10.default.relative(rootDir, filePath));
5110
5290
  return {
5111
5291
  ...data,
5112
5292
  body: parsed.content || "",
5113
- filePath: import_node_path9.default.relative(rootDir, filePath)
5293
+ filePath: import_node_path10.default.relative(rootDir, filePath)
5114
5294
  };
5115
5295
  }
5116
5296
  function walk(dir) {
5117
5297
  const out = [];
5118
- if (!import_node_fs14.default.existsSync(dir)) return out;
5119
- const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
5298
+ if (!import_node_fs15.default.existsSync(dir)) return out;
5299
+ const entries = import_node_fs15.default.readdirSync(dir, { withFileTypes: true });
5120
5300
  for (const entry of entries) {
5121
- const file = import_node_path9.default.join(dir, entry.name);
5301
+ const file = import_node_path10.default.join(dir, entry.name);
5122
5302
  if (entry.isDirectory()) out.push(...walk(file));
5123
5303
  if (entry.isFile() && file.endsWith(".md")) out.push(file);
5124
5304
  }
5125
5305
  return out;
5126
5306
  }
5127
5307
  function itemPath(type, id, rootDir, workspaceDir) {
5128
- return import_node_path9.default.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type], `${id}.md`);
5308
+ return import_node_path10.default.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type], `${id}.md`);
5129
5309
  }
5130
5310
  function normalizeFrontmatterValue(value) {
5131
5311
  if (value == null) return void 0;
@@ -5212,30 +5392,30 @@ function nextGeneratedId(config, title, existing) {
5212
5392
  }
5213
5393
  function ensureCoopLayout(rootDir) {
5214
5394
  const workspaceDir = preferredWorkspaceDir(rootDir);
5215
- const root = import_node_path9.default.join(rootDir, workspaceDir);
5216
- import_node_fs14.default.mkdirSync(root, { recursive: true });
5217
- import_node_fs14.default.mkdirSync(import_node_path9.default.join(root, "releases"), { recursive: true });
5218
- import_node_fs14.default.mkdirSync(import_node_path9.default.join(root, "plans"), { recursive: true });
5219
- import_node_fs14.default.mkdirSync(import_node_path9.default.join(root, "views"), { recursive: true });
5220
- import_node_fs14.default.mkdirSync(import_node_path9.default.join(root, "templates"), { recursive: true });
5395
+ const root = import_node_path10.default.join(rootDir, workspaceDir);
5396
+ import_node_fs15.default.mkdirSync(root, { recursive: true });
5397
+ import_node_fs15.default.mkdirSync(import_node_path10.default.join(root, "releases"), { recursive: true });
5398
+ import_node_fs15.default.mkdirSync(import_node_path10.default.join(root, "plans"), { recursive: true });
5399
+ import_node_fs15.default.mkdirSync(import_node_path10.default.join(root, "views"), { recursive: true });
5400
+ import_node_fs15.default.mkdirSync(import_node_path10.default.join(root, "templates"), { recursive: true });
5221
5401
  for (const dir of Object.values(ITEM_DIRS)) {
5222
- import_node_fs14.default.mkdirSync(import_node_path9.default.join(root, "backlog", dir), { recursive: true });
5402
+ import_node_fs15.default.mkdirSync(import_node_path10.default.join(root, "backlog", dir), { recursive: true });
5223
5403
  }
5224
- const configFile = import_node_path9.default.join(root, "config.yml");
5225
- if (!import_node_fs14.default.existsSync(configFile)) {
5226
- import_node_fs14.default.writeFileSync(configFile, configToString(DEFAULT_CONFIG3), "utf8");
5404
+ const configFile = import_node_path10.default.join(root, "config.yml");
5405
+ if (!import_node_fs15.default.existsSync(configFile)) {
5406
+ import_node_fs15.default.writeFileSync(configFile, configToString(DEFAULT_CONFIG3), "utf8");
5227
5407
  }
5228
5408
  }
5229
5409
  function loadState(rootDir) {
5230
5410
  const workspaceDir = detectWorkspaceDir(rootDir);
5231
5411
  if (!workspaceDir) throw missingConfigError(rootDir);
5232
5412
  const configPath = configPathFor(rootDir, workspaceDir);
5233
- if (!import_node_fs14.default.existsSync(configPath)) throw missingConfigError(rootDir);
5234
- const config = parseConfig(import_node_fs14.default.readFileSync(configPath, "utf8"));
5413
+ if (!import_node_fs15.default.existsSync(configPath)) throw missingConfigError(rootDir);
5414
+ const config = parseConfig(import_node_fs15.default.readFileSync(configPath, "utf8"));
5235
5415
  const items = [];
5236
5416
  const itemsById = /* @__PURE__ */ new Map();
5237
5417
  for (const type of ITEM_TYPES) {
5238
- const dir = import_node_path9.default.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type]);
5418
+ const dir = import_node_path10.default.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type]);
5239
5419
  const files = walk(dir);
5240
5420
  for (const file of files) {
5241
5421
  const item = parseItem(file, rootDir);
@@ -5319,21 +5499,21 @@ function createItem(rootDir, params) {
5319
5499
  parent_id: params.parent_id
5320
5500
  };
5321
5501
  const itemPathName = itemPath(params.type, item.id, rootDir, state.workspaceDir);
5322
- import_node_fs14.default.writeFileSync(itemPathName, serialize(item, params.body || ""), "utf8");
5502
+ import_node_fs15.default.writeFileSync(itemPathName, serialize(item, params.body || ""), "utf8");
5323
5503
  if ((config.id_strategy ?? "text") === "counter") {
5324
5504
  const numericMatch = /-(\d+)$/.exec(item.id);
5325
5505
  if (numericMatch) {
5326
5506
  const numericValue = Number(numericMatch[1]);
5327
5507
  if (Number.isInteger(numericValue) && numericValue >= (config.next_id ?? 1)) {
5328
5508
  config.next_id = numericValue + 1;
5329
- import_node_fs14.default.writeFileSync(configPathFor(rootDir, state.workspaceDir), configToString(config), "utf8");
5509
+ import_node_fs15.default.writeFileSync(configPathFor(rootDir, state.workspaceDir), configToString(config), "utf8");
5330
5510
  }
5331
5511
  }
5332
5512
  }
5333
5513
  return {
5334
5514
  ...item,
5335
5515
  body: params.body || "",
5336
- filePath: import_node_path9.default.relative(rootDir, itemPathName)
5516
+ filePath: import_node_path10.default.relative(rootDir, itemPathName)
5337
5517
  };
5338
5518
  }
5339
5519
  function updateItem(rootDir, id, patch) {
@@ -5359,8 +5539,8 @@ function updateItem(rootDir, id, patch) {
5359
5539
  };
5360
5540
  if (!ITEM_TYPES.includes(next.type)) throw new Error(`Unknown type ${next.type}.`);
5361
5541
  if (!ITEM_STATUSES.includes(next.status)) throw new Error(`Unknown status ${next.status}.`);
5362
- const filePath = import_node_path9.default.join(rootDir, existing.filePath);
5363
- import_node_fs14.default.writeFileSync(filePath, serialize(next, patch.body || existing.body), "utf8");
5542
+ const filePath = import_node_path10.default.join(rootDir, existing.filePath);
5543
+ import_node_fs15.default.writeFileSync(filePath, serialize(next, patch.body || existing.body), "utf8");
5364
5544
  return {
5365
5545
  ...next,
5366
5546
  body: patch.body || existing.body,
@@ -5376,7 +5556,7 @@ function deleteItem(rootDir, id) {
5376
5556
  if (children.length > 0) {
5377
5557
  throw new Error(`Cannot delete ${existing.id} because it has ${children.length} child item(s). Remove children first.`);
5378
5558
  }
5379
- import_node_fs14.default.unlinkSync(import_node_path9.default.join(rootDir, existing.filePath));
5559
+ import_node_fs15.default.unlinkSync(import_node_path10.default.join(rootDir, existing.filePath));
5380
5560
  return existing;
5381
5561
  }
5382
5562
  function renderAgentPrompt(item) {
@@ -5415,8 +5595,8 @@ function validateRepo(rootDir) {
5415
5595
  const errors = [];
5416
5596
  const warnings = [];
5417
5597
  const workspaceDir = detectWorkspaceDir(rootDir);
5418
- if (!workspaceDir || !import_node_fs14.default.existsSync(configPathFor(rootDir, workspaceDir))) {
5419
- errors.push("Missing .coop/config.yml. Run coop init first.");
5598
+ if (!workspaceDir || !import_node_fs15.default.existsSync(configPathFor(rootDir, workspaceDir))) {
5599
+ errors.push("Missing COOP config. Run coop init first.");
5420
5600
  return { valid: false, errors, warnings };
5421
5601
  }
5422
5602
  let state;
@@ -5490,6 +5670,11 @@ function validateRepo(rootDir) {
5490
5670
  compute_readiness_with_corrections,
5491
5671
  compute_score,
5492
5672
  compute_velocity,
5673
+ coop_project_config_path,
5674
+ coop_project_root,
5675
+ coop_projects_dir,
5676
+ coop_workspace_config_path,
5677
+ coop_workspace_dir,
5493
5678
  createItem,
5494
5679
  create_seeded_rng,
5495
5680
  critical_path_weight,
@@ -5501,6 +5686,7 @@ function validateRepo(rootDir) {
5501
5686
  effective_weekly_hours,
5502
5687
  effort_or_default,
5503
5688
  ensureCoopLayout,
5689
+ ensure_workspace_layout,
5504
5690
  executor_fit_weight,
5505
5691
  external_dependencies_for_task,
5506
5692
  extract_subgraph,
@@ -5509,7 +5695,11 @@ function validateRepo(rootDir) {
5509
5695
  getItemById,
5510
5696
  get_remaining_tokens,
5511
5697
  get_user_role,
5698
+ has_legacy_project_layout,
5699
+ has_v2_projects_layout,
5512
5700
  is_external_dependency,
5701
+ is_project_initialized,
5702
+ list_projects,
5513
5703
  loadState,
5514
5704
  load_auth_config,
5515
5705
  load_completed_runs,
@@ -5534,9 +5724,14 @@ function validateRepo(rootDir) {
5534
5724
  pert_stddev,
5535
5725
  priority_weight,
5536
5726
  queryItems,
5727
+ read_project_config,
5537
5728
  read_schema_version,
5729
+ read_workspace_config,
5538
5730
  renderAgentPrompt,
5731
+ repo_default_project_id,
5732
+ repo_default_project_name,
5539
5733
  resolve_external_dependencies,
5734
+ resolve_project,
5540
5735
  risk_penalty,
5541
5736
  run_hook,
5542
5737
  run_monte_carlo_chunk,
@@ -5565,5 +5760,6 @@ function validateRepo(rootDir) {
5565
5760
  validate_transition,
5566
5761
  writeTask,
5567
5762
  writeYamlFile,
5568
- write_schema_version
5763
+ write_schema_version,
5764
+ write_workspace_config
5569
5765
  });
package/dist/index.d.cts CHANGED
@@ -293,6 +293,15 @@ interface TaskPlanning {
293
293
  depends_on?: string[];
294
294
  tags?: string[];
295
295
  delivery?: string | null;
296
+ acceptance?: string[];
297
+ tests_required?: string[];
298
+ origin?: {
299
+ authority_refs?: string[];
300
+ derived_refs?: string[];
301
+ promoted_from?: string[];
302
+ promoted_to?: string[];
303
+ snapshot_sha256?: string;
304
+ };
296
305
  }
297
306
  interface TaskEstimate {
298
307
  optimistic_hours: number;
@@ -1386,6 +1395,39 @@ interface ValidationResult {
1386
1395
  }
1387
1396
  declare function validate(task: Task, context?: ValidationContext): ValidationResult;
1388
1397
 
1398
+ type CoopWorkspaceConfig = {
1399
+ version?: number;
1400
+ current_project?: string;
1401
+ };
1402
+ type CoopProjectRef = {
1403
+ id: string;
1404
+ name: string;
1405
+ aliases: string[];
1406
+ root: string;
1407
+ repo_root: string;
1408
+ layout: "legacy" | "v2";
1409
+ };
1410
+ type ResolveProjectOptions = {
1411
+ project?: string;
1412
+ require?: boolean;
1413
+ };
1414
+ declare function coop_workspace_dir(repoRoot: string): string;
1415
+ declare function coop_projects_dir(repoRoot: string): string;
1416
+ declare function coop_workspace_config_path(repoRoot: string): string;
1417
+ declare function coop_project_root(repoRoot: string, projectId: string): string;
1418
+ declare function coop_project_config_path(projectRoot: string): string;
1419
+ declare function repo_default_project_id(repoRoot: string): string;
1420
+ declare function repo_default_project_name(repoRoot: string): string;
1421
+ declare function has_v2_projects_layout(repoRoot: string): boolean;
1422
+ declare function has_legacy_project_layout(repoRoot: string): boolean;
1423
+ declare function read_workspace_config(repoRoot: string): CoopWorkspaceConfig;
1424
+ declare function write_workspace_config(repoRoot: string, config: CoopWorkspaceConfig): void;
1425
+ declare function read_project_config(projectRoot: string): CoopConfig;
1426
+ declare function list_projects(repoRoot: string): CoopProjectRef[];
1427
+ declare function resolve_project(repoRoot: string, options?: ResolveProjectOptions): CoopProjectRef;
1428
+ declare function ensure_workspace_layout(repoRoot: string): string;
1429
+ declare function is_project_initialized(projectRoot: string): boolean;
1430
+
1389
1431
  /**
1390
1432
  * Finds the nearest workspace root that contains package + workspace metadata.
1391
1433
  * [SPEC: Architecture v2.0 §2]
@@ -1452,4 +1494,4 @@ declare function validateRepo(rootDir: string): {
1452
1494
  warnings: string[];
1453
1495
  };
1454
1496
 
1455
- export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, is_external_dependency, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_schema_version, renderAgentPrompt, resolve_external_dependencies, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version };
1497
+ export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
package/dist/index.d.ts CHANGED
@@ -293,6 +293,15 @@ interface TaskPlanning {
293
293
  depends_on?: string[];
294
294
  tags?: string[];
295
295
  delivery?: string | null;
296
+ acceptance?: string[];
297
+ tests_required?: string[];
298
+ origin?: {
299
+ authority_refs?: string[];
300
+ derived_refs?: string[];
301
+ promoted_from?: string[];
302
+ promoted_to?: string[];
303
+ snapshot_sha256?: string;
304
+ };
296
305
  }
297
306
  interface TaskEstimate {
298
307
  optimistic_hours: number;
@@ -1386,6 +1395,39 @@ interface ValidationResult {
1386
1395
  }
1387
1396
  declare function validate(task: Task, context?: ValidationContext): ValidationResult;
1388
1397
 
1398
+ type CoopWorkspaceConfig = {
1399
+ version?: number;
1400
+ current_project?: string;
1401
+ };
1402
+ type CoopProjectRef = {
1403
+ id: string;
1404
+ name: string;
1405
+ aliases: string[];
1406
+ root: string;
1407
+ repo_root: string;
1408
+ layout: "legacy" | "v2";
1409
+ };
1410
+ type ResolveProjectOptions = {
1411
+ project?: string;
1412
+ require?: boolean;
1413
+ };
1414
+ declare function coop_workspace_dir(repoRoot: string): string;
1415
+ declare function coop_projects_dir(repoRoot: string): string;
1416
+ declare function coop_workspace_config_path(repoRoot: string): string;
1417
+ declare function coop_project_root(repoRoot: string, projectId: string): string;
1418
+ declare function coop_project_config_path(projectRoot: string): string;
1419
+ declare function repo_default_project_id(repoRoot: string): string;
1420
+ declare function repo_default_project_name(repoRoot: string): string;
1421
+ declare function has_v2_projects_layout(repoRoot: string): boolean;
1422
+ declare function has_legacy_project_layout(repoRoot: string): boolean;
1423
+ declare function read_workspace_config(repoRoot: string): CoopWorkspaceConfig;
1424
+ declare function write_workspace_config(repoRoot: string, config: CoopWorkspaceConfig): void;
1425
+ declare function read_project_config(projectRoot: string): CoopConfig;
1426
+ declare function list_projects(repoRoot: string): CoopProjectRef[];
1427
+ declare function resolve_project(repoRoot: string, options?: ResolveProjectOptions): CoopProjectRef;
1428
+ declare function ensure_workspace_layout(repoRoot: string): string;
1429
+ declare function is_project_initialized(projectRoot: string): boolean;
1430
+
1389
1431
  /**
1390
1432
  * Finds the nearest workspace root that contains package + workspace metadata.
1391
1433
  * [SPEC: Architecture v2.0 §2]
@@ -1452,4 +1494,4 @@ declare function validateRepo(rootDir: string): {
1452
1494
  warnings: string[];
1453
1495
  };
1454
1496
 
1455
- export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, is_external_dependency, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_schema_version, renderAgentPrompt, resolve_external_dependencies, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version };
1497
+ export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
package/dist/index.js CHANGED
@@ -1966,6 +1966,9 @@ var TASK_FIELD_ORDER = [
1966
1966
  "depends_on",
1967
1967
  "tags",
1968
1968
  "delivery",
1969
+ "acceptance",
1970
+ "tests_required",
1971
+ "origin",
1969
1972
  "complexity",
1970
1973
  "determinism",
1971
1974
  "estimate",
@@ -3627,6 +3630,7 @@ import path8 from "path";
3627
3630
  var ID_PATTERN = /^[A-Z0-9]+(?:-[A-Z0-9]+)+$/;
3628
3631
  var ALIAS_PATTERN = /^[A-Z0-9]+(?:[.-][A-Z0-9]+)*$/;
3629
3632
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
3633
+ var SHA256_RE = /^[a-f0-9]{64}$/i;
3630
3634
  function error4(field, rule, message) {
3631
3635
  return { level: "error", field, rule, message };
3632
3636
  }
@@ -3640,6 +3644,23 @@ function isIsoDate(value) {
3640
3644
  }
3641
3645
  return date.toISOString().slice(0, 10) === value;
3642
3646
  }
3647
+ function validateStringArrayField(errors, field, value, options = {}) {
3648
+ if (value === void 0) {
3649
+ return;
3650
+ }
3651
+ if (!Array.isArray(value)) {
3652
+ errors.push(error4(field, `struct.${field}_array`, `Field '${field}' must be an array of strings.`));
3653
+ return;
3654
+ }
3655
+ for (const entry of value) {
3656
+ if (typeof entry !== "string" || entry.trim().length < (options.minLength ?? 1)) {
3657
+ errors.push(
3658
+ error4(field, `struct.${field}_string`, `Field '${field}' entries must be non-empty strings.`)
3659
+ );
3660
+ return;
3661
+ }
3662
+ }
3663
+ }
3643
3664
  function validateStructural(task, context = {}) {
3644
3665
  const errors = [];
3645
3666
  const required = ["id", "title", "type", "status", "created", "updated"];
@@ -3704,6 +3725,27 @@ function validateStructural(task, context = {}) {
3704
3725
  }
3705
3726
  }
3706
3727
  }
3728
+ validateStringArrayField(errors, "acceptance", task.acceptance);
3729
+ validateStringArrayField(errors, "tests_required", task.tests_required);
3730
+ if (task.origin !== void 0) {
3731
+ if (!task.origin || typeof task.origin !== "object" || Array.isArray(task.origin)) {
3732
+ errors.push(error4("origin", "struct.origin_object", "Field 'origin' must be an object."));
3733
+ } else {
3734
+ validateStringArrayField(errors, "origin.authority_refs", task.origin.authority_refs);
3735
+ validateStringArrayField(errors, "origin.derived_refs", task.origin.derived_refs);
3736
+ validateStringArrayField(errors, "origin.promoted_from", task.origin.promoted_from);
3737
+ validateStringArrayField(errors, "origin.promoted_to", task.origin.promoted_to);
3738
+ if (task.origin.snapshot_sha256 !== void 0 && (typeof task.origin.snapshot_sha256 !== "string" || !SHA256_RE.test(task.origin.snapshot_sha256))) {
3739
+ errors.push(
3740
+ error4(
3741
+ "origin.snapshot_sha256",
3742
+ "struct.origin_snapshot_sha256",
3743
+ "Field 'origin.snapshot_sha256' must be a 64-character SHA-256 hex string."
3744
+ )
3745
+ );
3746
+ }
3747
+ }
3748
+ }
3707
3749
  return errors;
3708
3750
  }
3709
3751
 
@@ -3791,9 +3833,131 @@ function validate(task, context = {}) {
3791
3833
  };
3792
3834
  }
3793
3835
 
3794
- // src/core.ts
3836
+ // src/workspace.ts
3795
3837
  import fs13 from "fs";
3796
3838
  import path9 from "path";
3839
+ var COOP_DIR_NAME = ".coop";
3840
+ function sanitizeProjectId(value, fallback) {
3841
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
3842
+ return normalized || fallback;
3843
+ }
3844
+ function coop_workspace_dir(repoRoot) {
3845
+ return path9.join(path9.resolve(repoRoot), COOP_DIR_NAME);
3846
+ }
3847
+ function coop_projects_dir(repoRoot) {
3848
+ return path9.join(coop_workspace_dir(repoRoot), "projects");
3849
+ }
3850
+ function coop_workspace_config_path(repoRoot) {
3851
+ return path9.join(coop_workspace_dir(repoRoot), "config.yml");
3852
+ }
3853
+ function coop_project_root(repoRoot, projectId) {
3854
+ return path9.join(coop_projects_dir(repoRoot), projectId);
3855
+ }
3856
+ function coop_project_config_path(projectRoot) {
3857
+ return path9.join(projectRoot, "config.yml");
3858
+ }
3859
+ function repo_default_project_id(repoRoot) {
3860
+ return sanitizeProjectId(path9.basename(path9.resolve(repoRoot)), "workspace");
3861
+ }
3862
+ function repo_default_project_name(repoRoot) {
3863
+ const base = path9.basename(path9.resolve(repoRoot)).trim();
3864
+ return base || "COOP Workspace";
3865
+ }
3866
+ function has_v2_projects_layout(repoRoot) {
3867
+ return fs13.existsSync(coop_projects_dir(repoRoot));
3868
+ }
3869
+ function has_legacy_project_layout(repoRoot) {
3870
+ const workspaceDir = coop_workspace_dir(repoRoot);
3871
+ return fs13.existsSync(workspaceDir) && fs13.existsSync(path9.join(workspaceDir, "config.yml")) && !fs13.existsSync(coop_projects_dir(repoRoot));
3872
+ }
3873
+ function read_workspace_config(repoRoot) {
3874
+ const configPath = coop_workspace_config_path(repoRoot);
3875
+ if (!fs13.existsSync(configPath) || has_legacy_project_layout(repoRoot)) {
3876
+ return { version: 2 };
3877
+ }
3878
+ return parseYamlFile(configPath);
3879
+ }
3880
+ function write_workspace_config(repoRoot, config) {
3881
+ fs13.mkdirSync(coop_workspace_dir(repoRoot), { recursive: true });
3882
+ writeYamlFile(coop_workspace_config_path(repoRoot), {
3883
+ version: config.version ?? 2,
3884
+ ...config.current_project ? { current_project: config.current_project } : {}
3885
+ });
3886
+ }
3887
+ function read_project_config(projectRoot) {
3888
+ return parseYamlFile(coop_project_config_path(projectRoot));
3889
+ }
3890
+ function project_ref_from_config(repoRoot, projectRoot, layout) {
3891
+ const config = read_project_config(projectRoot);
3892
+ const repoName = repo_default_project_name(repoRoot);
3893
+ const fallbackId = repo_default_project_id(repoRoot);
3894
+ return {
3895
+ id: sanitizeProjectId(config.project?.id ?? fallbackId, fallbackId),
3896
+ name: config.project?.name?.trim() || repoName,
3897
+ aliases: Array.isArray(config.project?.aliases) ? config.project.aliases.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [],
3898
+ root: projectRoot,
3899
+ repo_root: path9.resolve(repoRoot),
3900
+ layout
3901
+ };
3902
+ }
3903
+ function list_projects(repoRoot) {
3904
+ if (has_v2_projects_layout(repoRoot)) {
3905
+ const projectsDir = coop_projects_dir(repoRoot);
3906
+ return fs13.readdirSync(projectsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path9.join(projectsDir, entry.name)).filter((projectRoot) => fs13.existsSync(coop_project_config_path(projectRoot))).map((projectRoot) => project_ref_from_config(repoRoot, projectRoot, "v2")).sort((a, b) => a.id.localeCompare(b.id));
3907
+ }
3908
+ if (has_legacy_project_layout(repoRoot)) {
3909
+ return [project_ref_from_config(repoRoot, coop_workspace_dir(repoRoot), "legacy")];
3910
+ }
3911
+ return [];
3912
+ }
3913
+ function resolve_project(repoRoot, options = {}) {
3914
+ const projects = list_projects(repoRoot);
3915
+ const requested = options.project?.trim().toLowerCase();
3916
+ if (requested) {
3917
+ const match = projects.find(
3918
+ (project) => project.id.toLowerCase() === requested || project.name.toLowerCase() === requested || project.aliases.some((alias) => alias.toLowerCase() === requested)
3919
+ );
3920
+ if (!match) {
3921
+ throw new Error(`Project '${options.project}' not found.`);
3922
+ }
3923
+ return match;
3924
+ }
3925
+ if (projects.length === 1) {
3926
+ return projects[0];
3927
+ }
3928
+ const workspaceConfig = read_workspace_config(repoRoot);
3929
+ if (workspaceConfig.current_project) {
3930
+ const match = projects.find((project) => project.id === workspaceConfig.current_project);
3931
+ if (match) return match;
3932
+ }
3933
+ if (!options.require && projects.length === 0) {
3934
+ return {
3935
+ id: repo_default_project_id(repoRoot),
3936
+ name: repo_default_project_name(repoRoot),
3937
+ aliases: [],
3938
+ root: coop_project_root(repoRoot, repo_default_project_id(repoRoot)),
3939
+ repo_root: path9.resolve(repoRoot),
3940
+ layout: "v2"
3941
+ };
3942
+ }
3943
+ if (projects.length === 0) {
3944
+ throw new Error("No COOP project found. Run 'coop init'.");
3945
+ }
3946
+ throw new Error("Multiple COOP projects found. Pass --project <id> or run 'coop project use <id>'.");
3947
+ }
3948
+ function ensure_workspace_layout(repoRoot) {
3949
+ const workspaceDir = coop_workspace_dir(repoRoot);
3950
+ fs13.mkdirSync(workspaceDir, { recursive: true });
3951
+ fs13.mkdirSync(coop_projects_dir(repoRoot), { recursive: true });
3952
+ return workspaceDir;
3953
+ }
3954
+ function is_project_initialized(projectRoot) {
3955
+ return fs13.existsSync(coop_project_config_path(projectRoot));
3956
+ }
3957
+
3958
+ // src/core.ts
3959
+ import fs14 from "fs";
3960
+ import path10 from "path";
3797
3961
  import matter from "gray-matter";
3798
3962
 
3799
3963
  // src/types.ts
@@ -3819,17 +3983,17 @@ function toIdKey(value) {
3819
3983
  return value.trim().toUpperCase();
3820
3984
  }
3821
3985
  function repoRootByPackage(cwd) {
3822
- let current = path9.resolve(cwd);
3986
+ let current = path10.resolve(cwd);
3823
3987
  let lastWorkspaceRoot = null;
3824
3988
  while (true) {
3825
- const packageJson = path9.join(current, "package.json");
3826
- const workspaceYaml = path9.join(current, "pnpm-workspace.yaml");
3827
- if (fs13.existsSync(packageJson) && fs13.existsSync(workspaceYaml)) {
3989
+ const packageJson = path10.join(current, "package.json");
3990
+ const workspaceYaml = path10.join(current, "pnpm-workspace.yaml");
3991
+ if (fs14.existsSync(packageJson) && fs14.existsSync(workspaceYaml)) {
3828
3992
  lastWorkspaceRoot = current;
3829
- const hasCoop = fs13.existsSync(path9.join(current, COOP_DIR, "config.yml"));
3993
+ const hasCoop = fs14.existsSync(path10.join(current, COOP_DIR, "config.yml"));
3830
3994
  if (hasCoop) return current;
3831
3995
  }
3832
- const parent = path9.dirname(current);
3996
+ const parent = path10.dirname(current);
3833
3997
  if (parent === current) return lastWorkspaceRoot;
3834
3998
  current = parent;
3835
3999
  }
@@ -3838,24 +4002,24 @@ function findRepoRoot(cwd = process.cwd()) {
3838
4002
  return repoRootByPackage(cwd);
3839
4003
  }
3840
4004
  function configPathFor(rootDir, workspaceDir) {
3841
- return path9.join(rootDir, workspaceDir, "config.yml");
4005
+ return path10.join(rootDir, workspaceDir, "config.yml");
3842
4006
  }
3843
4007
  function backlogPathFor(rootDir, workspaceDir) {
3844
- return path9.join(rootDir, workspaceDir, "backlog");
4008
+ return path10.join(rootDir, workspaceDir, "backlog");
3845
4009
  }
3846
4010
  function releasesPathFor(rootDir, workspaceDir) {
3847
- return path9.join(rootDir, workspaceDir, "releases");
4011
+ return path10.join(rootDir, workspaceDir, "releases");
3848
4012
  }
3849
4013
  function detectWorkspaceDir(rootDir) {
3850
- if (fs13.existsSync(configPathFor(rootDir, COOP_DIR))) return COOP_DIR;
3851
- if (fs13.existsSync(path9.join(rootDir, COOP_DIR))) return COOP_DIR;
4014
+ if (fs14.existsSync(configPathFor(rootDir, COOP_DIR))) return COOP_DIR;
4015
+ if (fs14.existsSync(path10.join(rootDir, COOP_DIR))) return COOP_DIR;
3852
4016
  return null;
3853
4017
  }
3854
4018
  function preferredWorkspaceDir(rootDir) {
3855
4019
  return detectWorkspaceDir(rootDir) ?? COOP_DIR;
3856
4020
  }
3857
4021
  function missingConfigError(rootDir) {
3858
- const coopConfig = path9.relative(rootDir, configPathFor(rootDir, COOP_DIR));
4022
+ const coopConfig = path10.relative(rootDir, configPathFor(rootDir, COOP_DIR));
3859
4023
  return new Error(`COOP config missing at ${coopConfig}. Run: coop init`);
3860
4024
  }
3861
4025
  function parseConfig(raw) {
@@ -3901,10 +4065,10 @@ function configToString(config) {
3901
4065
  return lines.join("\n");
3902
4066
  }
3903
4067
  function toPortablePath(value) {
3904
- return value.split(path9.sep).join("/");
4068
+ return value.split(path10.sep).join("/");
3905
4069
  }
3906
4070
  function ensureReleasesDir(rootDir, workspaceDir) {
3907
- fs13.mkdirSync(releasesPathFor(rootDir, workspaceDir), { recursive: true });
4071
+ fs14.mkdirSync(releasesPathFor(rootDir, workspaceDir), { recursive: true });
3908
4072
  }
3909
4073
  function releaseHeader(date) {
3910
4074
  return `## ${date}`;
@@ -3926,12 +4090,12 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
3926
4090
  ensureReleasesDir(rootDir, workspaceDir);
3927
4091
  const now = /* @__PURE__ */ new Date();
3928
4092
  const date = now.toISOString().slice(0, 10);
3929
- const releasePath = path9.join(releasesPathFor(rootDir, workspaceDir), `${date}.md`);
4093
+ const releasePath = path10.join(releasesPathFor(rootDir, workspaceDir), `${date}.md`);
3930
4094
  const heading = "# COOP Release Notes";
3931
4095
  const dayHeader = releaseHeader(date);
3932
4096
  const entry = releaseEntryLine(item, previousStatus, nextStatus);
3933
- if (!fs13.existsSync(releasePath)) {
3934
- fs13.writeFileSync(
4097
+ if (!fs14.existsSync(releasePath)) {
4098
+ fs14.writeFileSync(
3935
4099
  releasePath,
3936
4100
  [
3937
4101
  `${heading}
@@ -3944,10 +4108,10 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
3944
4108
  ].join("\n"),
3945
4109
  "utf8"
3946
4110
  );
3947
- return toPortablePath(path9.relative(rootDir, releasePath));
4111
+ return toPortablePath(path10.relative(rootDir, releasePath));
3948
4112
  }
3949
- const existing = fs13.readFileSync(releasePath, "utf8");
3950
- if (hasReleaseEntry(existing, item.id)) return toPortablePath(path9.relative(rootDir, releasePath));
4113
+ const existing = fs14.readFileSync(releasePath, "utf8");
4114
+ if (hasReleaseEntry(existing, item.id)) return toPortablePath(path10.relative(rootDir, releasePath));
3951
4115
  let nextContent = existing;
3952
4116
  if (!existing.includes(`## ${date}`)) {
3953
4117
  if (!nextContent.endsWith("\n")) nextContent += "\n";
@@ -3957,9 +4121,9 @@ function appendReleaseEntry(rootDir, workspaceDir, item, previousStatus, nextSta
3957
4121
  if (!nextContent.endsWith("\n")) nextContent += "\n";
3958
4122
  nextContent += `${entry}
3959
4123
  `;
3960
- fs13.writeFileSync(releasePath, `${nextContent}
4124
+ fs14.writeFileSync(releasePath, `${nextContent}
3961
4125
  `, "utf8");
3962
- return toPortablePath(path9.relative(rootDir, releasePath));
4126
+ return toPortablePath(path10.relative(rootDir, releasePath));
3963
4127
  }
3964
4128
  function completeItem(rootDir, id) {
3965
4129
  const state = loadState(rootDir);
@@ -4037,28 +4201,28 @@ function validateAndNormalize(data, sourceFile) {
4037
4201
  };
4038
4202
  }
4039
4203
  function parseItem(filePath, rootDir) {
4040
- const raw = fs13.readFileSync(filePath, "utf8");
4204
+ const raw = fs14.readFileSync(filePath, "utf8");
4041
4205
  const parsed = matter(raw);
4042
- const data = validateAndNormalize(parsed.data, path9.relative(rootDir, filePath));
4206
+ const data = validateAndNormalize(parsed.data, path10.relative(rootDir, filePath));
4043
4207
  return {
4044
4208
  ...data,
4045
4209
  body: parsed.content || "",
4046
- filePath: path9.relative(rootDir, filePath)
4210
+ filePath: path10.relative(rootDir, filePath)
4047
4211
  };
4048
4212
  }
4049
4213
  function walk(dir) {
4050
4214
  const out = [];
4051
- if (!fs13.existsSync(dir)) return out;
4052
- const entries = fs13.readdirSync(dir, { withFileTypes: true });
4215
+ if (!fs14.existsSync(dir)) return out;
4216
+ const entries = fs14.readdirSync(dir, { withFileTypes: true });
4053
4217
  for (const entry of entries) {
4054
- const file = path9.join(dir, entry.name);
4218
+ const file = path10.join(dir, entry.name);
4055
4219
  if (entry.isDirectory()) out.push(...walk(file));
4056
4220
  if (entry.isFile() && file.endsWith(".md")) out.push(file);
4057
4221
  }
4058
4222
  return out;
4059
4223
  }
4060
4224
  function itemPath(type, id, rootDir, workspaceDir) {
4061
- return path9.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type], `${id}.md`);
4225
+ return path10.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type], `${id}.md`);
4062
4226
  }
4063
4227
  function normalizeFrontmatterValue(value) {
4064
4228
  if (value == null) return void 0;
@@ -4145,30 +4309,30 @@ function nextGeneratedId(config, title, existing) {
4145
4309
  }
4146
4310
  function ensureCoopLayout(rootDir) {
4147
4311
  const workspaceDir = preferredWorkspaceDir(rootDir);
4148
- const root = path9.join(rootDir, workspaceDir);
4149
- fs13.mkdirSync(root, { recursive: true });
4150
- fs13.mkdirSync(path9.join(root, "releases"), { recursive: true });
4151
- fs13.mkdirSync(path9.join(root, "plans"), { recursive: true });
4152
- fs13.mkdirSync(path9.join(root, "views"), { recursive: true });
4153
- fs13.mkdirSync(path9.join(root, "templates"), { recursive: true });
4312
+ const root = path10.join(rootDir, workspaceDir);
4313
+ fs14.mkdirSync(root, { recursive: true });
4314
+ fs14.mkdirSync(path10.join(root, "releases"), { recursive: true });
4315
+ fs14.mkdirSync(path10.join(root, "plans"), { recursive: true });
4316
+ fs14.mkdirSync(path10.join(root, "views"), { recursive: true });
4317
+ fs14.mkdirSync(path10.join(root, "templates"), { recursive: true });
4154
4318
  for (const dir of Object.values(ITEM_DIRS)) {
4155
- fs13.mkdirSync(path9.join(root, "backlog", dir), { recursive: true });
4319
+ fs14.mkdirSync(path10.join(root, "backlog", dir), { recursive: true });
4156
4320
  }
4157
- const configFile = path9.join(root, "config.yml");
4158
- if (!fs13.existsSync(configFile)) {
4159
- fs13.writeFileSync(configFile, configToString(DEFAULT_CONFIG2), "utf8");
4321
+ const configFile = path10.join(root, "config.yml");
4322
+ if (!fs14.existsSync(configFile)) {
4323
+ fs14.writeFileSync(configFile, configToString(DEFAULT_CONFIG2), "utf8");
4160
4324
  }
4161
4325
  }
4162
4326
  function loadState(rootDir) {
4163
4327
  const workspaceDir = detectWorkspaceDir(rootDir);
4164
4328
  if (!workspaceDir) throw missingConfigError(rootDir);
4165
4329
  const configPath = configPathFor(rootDir, workspaceDir);
4166
- if (!fs13.existsSync(configPath)) throw missingConfigError(rootDir);
4167
- const config = parseConfig(fs13.readFileSync(configPath, "utf8"));
4330
+ if (!fs14.existsSync(configPath)) throw missingConfigError(rootDir);
4331
+ const config = parseConfig(fs14.readFileSync(configPath, "utf8"));
4168
4332
  const items = [];
4169
4333
  const itemsById = /* @__PURE__ */ new Map();
4170
4334
  for (const type of ITEM_TYPES) {
4171
- const dir = path9.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type]);
4335
+ const dir = path10.join(backlogPathFor(rootDir, workspaceDir), ITEM_DIRS[type]);
4172
4336
  const files = walk(dir);
4173
4337
  for (const file of files) {
4174
4338
  const item = parseItem(file, rootDir);
@@ -4252,21 +4416,21 @@ function createItem(rootDir, params) {
4252
4416
  parent_id: params.parent_id
4253
4417
  };
4254
4418
  const itemPathName = itemPath(params.type, item.id, rootDir, state.workspaceDir);
4255
- fs13.writeFileSync(itemPathName, serialize(item, params.body || ""), "utf8");
4419
+ fs14.writeFileSync(itemPathName, serialize(item, params.body || ""), "utf8");
4256
4420
  if ((config.id_strategy ?? "text") === "counter") {
4257
4421
  const numericMatch = /-(\d+)$/.exec(item.id);
4258
4422
  if (numericMatch) {
4259
4423
  const numericValue = Number(numericMatch[1]);
4260
4424
  if (Number.isInteger(numericValue) && numericValue >= (config.next_id ?? 1)) {
4261
4425
  config.next_id = numericValue + 1;
4262
- fs13.writeFileSync(configPathFor(rootDir, state.workspaceDir), configToString(config), "utf8");
4426
+ fs14.writeFileSync(configPathFor(rootDir, state.workspaceDir), configToString(config), "utf8");
4263
4427
  }
4264
4428
  }
4265
4429
  }
4266
4430
  return {
4267
4431
  ...item,
4268
4432
  body: params.body || "",
4269
- filePath: path9.relative(rootDir, itemPathName)
4433
+ filePath: path10.relative(rootDir, itemPathName)
4270
4434
  };
4271
4435
  }
4272
4436
  function updateItem(rootDir, id, patch) {
@@ -4292,8 +4456,8 @@ function updateItem(rootDir, id, patch) {
4292
4456
  };
4293
4457
  if (!ITEM_TYPES.includes(next.type)) throw new Error(`Unknown type ${next.type}.`);
4294
4458
  if (!ITEM_STATUSES.includes(next.status)) throw new Error(`Unknown status ${next.status}.`);
4295
- const filePath = path9.join(rootDir, existing.filePath);
4296
- fs13.writeFileSync(filePath, serialize(next, patch.body || existing.body), "utf8");
4459
+ const filePath = path10.join(rootDir, existing.filePath);
4460
+ fs14.writeFileSync(filePath, serialize(next, patch.body || existing.body), "utf8");
4297
4461
  return {
4298
4462
  ...next,
4299
4463
  body: patch.body || existing.body,
@@ -4309,7 +4473,7 @@ function deleteItem(rootDir, id) {
4309
4473
  if (children.length > 0) {
4310
4474
  throw new Error(`Cannot delete ${existing.id} because it has ${children.length} child item(s). Remove children first.`);
4311
4475
  }
4312
- fs13.unlinkSync(path9.join(rootDir, existing.filePath));
4476
+ fs14.unlinkSync(path10.join(rootDir, existing.filePath));
4313
4477
  return existing;
4314
4478
  }
4315
4479
  function renderAgentPrompt(item) {
@@ -4348,8 +4512,8 @@ function validateRepo(rootDir) {
4348
4512
  const errors = [];
4349
4513
  const warnings = [];
4350
4514
  const workspaceDir = detectWorkspaceDir(rootDir);
4351
- if (!workspaceDir || !fs13.existsSync(configPathFor(rootDir, workspaceDir))) {
4352
- errors.push("Missing .coop/config.yml. Run coop init first.");
4515
+ if (!workspaceDir || !fs14.existsSync(configPathFor(rootDir, workspaceDir))) {
4516
+ errors.push("Missing COOP config. Run coop init first.");
4353
4517
  return { valid: false, errors, warnings };
4354
4518
  }
4355
4519
  let state;
@@ -4422,6 +4586,11 @@ export {
4422
4586
  compute_readiness_with_corrections,
4423
4587
  compute_score,
4424
4588
  compute_velocity,
4589
+ coop_project_config_path,
4590
+ coop_project_root,
4591
+ coop_projects_dir,
4592
+ coop_workspace_config_path,
4593
+ coop_workspace_dir,
4425
4594
  createItem,
4426
4595
  create_seeded_rng,
4427
4596
  critical_path_weight,
@@ -4433,6 +4602,7 @@ export {
4433
4602
  effective_weekly_hours,
4434
4603
  effort_or_default,
4435
4604
  ensureCoopLayout,
4605
+ ensure_workspace_layout,
4436
4606
  executor_fit_weight,
4437
4607
  external_dependencies_for_task,
4438
4608
  extract_subgraph,
@@ -4441,7 +4611,11 @@ export {
4441
4611
  getItemById,
4442
4612
  get_remaining_tokens,
4443
4613
  get_user_role,
4614
+ has_legacy_project_layout,
4615
+ has_v2_projects_layout,
4444
4616
  is_external_dependency,
4617
+ is_project_initialized,
4618
+ list_projects,
4445
4619
  loadState,
4446
4620
  load_auth_config,
4447
4621
  load_completed_runs,
@@ -4466,9 +4640,14 @@ export {
4466
4640
  pert_stddev,
4467
4641
  priority_weight,
4468
4642
  queryItems,
4643
+ read_project_config,
4469
4644
  read_schema_version,
4645
+ read_workspace_config,
4470
4646
  renderAgentPrompt,
4647
+ repo_default_project_id,
4648
+ repo_default_project_name,
4471
4649
  resolve_external_dependencies,
4650
+ resolve_project,
4472
4651
  risk_penalty,
4473
4652
  run_hook,
4474
4653
  run_monte_carlo_chunk,
@@ -4497,5 +4676,6 @@ export {
4497
4676
  validate_transition,
4498
4677
  writeTask,
4499
4678
  writeYamlFile,
4500
- write_schema_version
4679
+ write_schema_version,
4680
+ write_workspace_config
4501
4681
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kitsy/coop-core",
3
3
  "description": "Core models, parser, validator, graph, and planning engine for COOP.",
4
- "version": "1.0.0",
4
+ "version": "2.1.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {