@devory/core 0.0.1 → 0.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/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @devory/core
2
+
3
+ Shared types, parsing utilities, and path configuration for [Devory](https://devory.ai).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @devory/core
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### Task parsing
14
+
15
+ ```ts
16
+ import { parseFrontmatter } from '@devory/core'
17
+
18
+ const result = parseFrontmatter(fileContent)
19
+ // result.meta — typed TaskMeta fields
20
+ // result.body — markdown body after frontmatter
21
+ ```
22
+
23
+ ### Factory environment
24
+
25
+ ```ts
26
+ import { resolveFactoryEnvironment, factoryPaths } from '@devory/core'
27
+
28
+ const env = resolveFactoryEnvironment()
29
+ const paths = factoryPaths(env.root)
30
+ // paths.tasksDir, paths.runsDir, paths.artifactsDir, etc.
31
+ ```
32
+
33
+ ## Types
34
+
35
+ - `TaskMeta` — frontmatter fields for a Devory task file
36
+ - `FactoryEnvironment` — resolved root, mode, and source
37
+ - `FactoryPaths` — all well-known directories in a factory workspace
38
+
39
+ ## Requirements
40
+
41
+ - Node.js 18+
package/dist/index.js ADDED
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ factoryPaths: () => factoryPaths,
34
+ findFactoryContextDir: () => findFactoryContextDir,
35
+ parseFrontmatter: () => parseFrontmatter,
36
+ resolveFactoryEnvironment: () => resolveFactoryEnvironment,
37
+ resolveFactoryMode: () => resolveFactoryMode,
38
+ resolveFactoryRoot: () => resolveFactoryRoot
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/parse.ts
43
+ function parseFrontmatter(content) {
44
+ const lines = content.split("\n");
45
+ if (lines[0]?.trim() !== "---") {
46
+ return { meta: {}, body: content };
47
+ }
48
+ const closeIdx = lines.indexOf("---", 1);
49
+ if (closeIdx === -1) {
50
+ return { meta: {}, body: content };
51
+ }
52
+ const yamlLines = lines.slice(1, closeIdx);
53
+ const body = lines.slice(closeIdx + 1).join("\n");
54
+ const meta = {};
55
+ let currentKey = "";
56
+ for (const line of yamlLines) {
57
+ const listMatch = line.match(/^\s+-\s+(.*)/);
58
+ const kvMatch = line.match(/^([\w_][\w_-]*):\s*(.*)/);
59
+ if (listMatch && currentKey) {
60
+ const arr = meta[currentKey];
61
+ if (Array.isArray(arr)) {
62
+ arr.push(listMatch[1].trim());
63
+ }
64
+ } else if (kvMatch) {
65
+ currentKey = kvMatch[1];
66
+ const val = kvMatch[2].trim();
67
+ if (val === "" || val === "[]") {
68
+ meta[currentKey] = [];
69
+ } else {
70
+ meta[currentKey] = val;
71
+ }
72
+ }
73
+ }
74
+ return { meta, body };
75
+ }
76
+
77
+ // src/factory-environment.ts
78
+ var fs = __toESM(require("fs"));
79
+ var path = __toESM(require("path"));
80
+ var FACTORY_MARKER = "FACTORY_CONTEXT.md";
81
+ function trimEnv(value) {
82
+ if (typeof value !== "string") return null;
83
+ const trimmed = value.trim();
84
+ return trimmed === "" ? null : trimmed;
85
+ }
86
+ function findFactoryContextDir(startDir) {
87
+ let current = path.resolve(startDir);
88
+ while (true) {
89
+ if (fs.existsSync(path.join(current, FACTORY_MARKER))) {
90
+ return current;
91
+ }
92
+ const parent = path.dirname(current);
93
+ if (parent === current) {
94
+ return null;
95
+ }
96
+ current = parent;
97
+ }
98
+ }
99
+ function resolveFactoryRoot(startDir = process.cwd()) {
100
+ const explicit = trimEnv(process.env.DEVORY_FACTORY_ROOT);
101
+ if (explicit) {
102
+ return { root: explicit, source: "env:DEVORY_FACTORY_ROOT" };
103
+ }
104
+ const legacy = trimEnv(process.env.FACTORY_ROOT);
105
+ if (legacy) {
106
+ return { root: legacy, source: "env:FACTORY_ROOT" };
107
+ }
108
+ const walked = findFactoryContextDir(startDir);
109
+ if (walked) {
110
+ return { root: walked, source: "git-walk" };
111
+ }
112
+ return { root: path.resolve(startDir), source: "cwd" };
113
+ }
114
+ function factoryPaths(root) {
115
+ return {
116
+ tasksDir: path.join(root, "tasks"),
117
+ runsDir: path.join(root, "runs"),
118
+ artifactsDir: path.join(root, "artifacts"),
119
+ contextFile: path.join(root, FACTORY_MARKER)
120
+ };
121
+ }
122
+ function resolveFactoryMode(env = process.env) {
123
+ const explicitMode = trimEnv(env.DEVORY_FACTORY_MODE) ?? trimEnv(env.FACTORY_MODE);
124
+ if (explicitMode === "hosted") return "hosted";
125
+ if (explicitMode === "local") return "local";
126
+ if (trimEnv(env.DEVORY_REMOTE_FACTORY_URL) || trimEnv(env.FACTORY_REMOTE_URL)) {
127
+ return "hosted";
128
+ }
129
+ return "local";
130
+ }
131
+ function resolveFactoryEnvironment(startDir = process.cwd(), env = process.env) {
132
+ const { root, source } = resolveFactoryRoot(startDir);
133
+ return {
134
+ root,
135
+ source,
136
+ mode: resolveFactoryMode(env),
137
+ paths: factoryPaths(root)
138
+ };
139
+ }
140
+ // Annotate the CommonJS export names for ESM import in node:
141
+ 0 && (module.exports = {
142
+ factoryPaths,
143
+ findFactoryContextDir,
144
+ parseFrontmatter,
145
+ resolveFactoryEnvironment,
146
+ resolveFactoryMode,
147
+ resolveFactoryRoot
148
+ });
package/package.json CHANGED
@@ -1,7 +1,24 @@
1
1
  {
2
2
  "name": "@devory/core",
3
- "version": "0.0.1",
4
- "description": "Devory core platform",
5
- "main": "index.js",
6
- "license": "MIT"
3
+ "version": "0.1.0",
4
+ "description": "Shared types, parsing utilities, and path configuration for AI Dev Factory",
5
+ "main": "./dist/index.js",
6
+ "types": "./src/index.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/",
12
+ "src/",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=cjs",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "devDependencies": {
20
+ "esbuild": "^0.20.0",
21
+ "tsx": "^4.19.2",
22
+ "typescript": "^5.7.3"
23
+ }
7
24
  }
@@ -0,0 +1,99 @@
1
+ import { afterEach, beforeEach, describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import * as fs from "fs";
4
+ import * as os from "os";
5
+ import * as path from "path";
6
+
7
+ import {
8
+ factoryPaths,
9
+ findFactoryContextDir,
10
+ resolveFactoryEnvironment,
11
+ resolveFactoryMode,
12
+ resolveFactoryRoot,
13
+ } from "./factory-environment.ts";
14
+
15
+ let tmpDir: string;
16
+
17
+ beforeEach(() => {
18
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "factory-env-test-"));
19
+ delete process.env.DEVORY_FACTORY_ROOT;
20
+ delete process.env.FACTORY_ROOT;
21
+ delete process.env.DEVORY_FACTORY_MODE;
22
+ delete process.env.FACTORY_MODE;
23
+ delete process.env.DEVORY_REMOTE_FACTORY_URL;
24
+ delete process.env.FACTORY_REMOTE_URL;
25
+ });
26
+
27
+ afterEach(() => {
28
+ fs.rmSync(tmpDir, { recursive: true, force: true });
29
+ delete process.env.DEVORY_FACTORY_ROOT;
30
+ delete process.env.FACTORY_ROOT;
31
+ delete process.env.DEVORY_FACTORY_MODE;
32
+ delete process.env.FACTORY_MODE;
33
+ delete process.env.DEVORY_REMOTE_FACTORY_URL;
34
+ delete process.env.FACTORY_REMOTE_URL;
35
+ });
36
+
37
+ describe("findFactoryContextDir", () => {
38
+ test("returns the containing directory when marker exists", () => {
39
+ fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
40
+ assert.equal(findFactoryContextDir(tmpDir), tmpDir);
41
+ });
42
+
43
+ test("walks up parent directories to find the factory marker", () => {
44
+ fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
45
+ const nested = path.join(tmpDir, "nested", "deep");
46
+ fs.mkdirSync(nested, { recursive: true });
47
+ assert.equal(findFactoryContextDir(nested), tmpDir);
48
+ });
49
+ });
50
+
51
+ describe("resolveFactoryRoot", () => {
52
+ test("uses DEVORY_FACTORY_ROOT first", () => {
53
+ process.env.DEVORY_FACTORY_ROOT = "/explicit/path";
54
+ process.env.FACTORY_ROOT = "/legacy/path";
55
+ assert.deepEqual(resolveFactoryRoot(tmpDir), {
56
+ root: "/explicit/path",
57
+ source: "env:DEVORY_FACTORY_ROOT",
58
+ });
59
+ });
60
+
61
+ test("walks to the factory marker when env vars are absent", () => {
62
+ fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
63
+ const nested = path.join(tmpDir, "deep");
64
+ fs.mkdirSync(nested);
65
+ assert.deepEqual(resolveFactoryRoot(nested), {
66
+ root: tmpDir,
67
+ source: "git-walk",
68
+ });
69
+ });
70
+ });
71
+
72
+ describe("resolveFactoryMode", () => {
73
+ test("defaults to local", () => {
74
+ assert.equal(resolveFactoryMode(), "local");
75
+ });
76
+
77
+ test("honors explicit hosted mode", () => {
78
+ process.env.DEVORY_FACTORY_MODE = "hosted";
79
+ assert.equal(resolveFactoryMode(), "hosted");
80
+ });
81
+
82
+ test("treats remote url configuration as hosted mode", () => {
83
+ process.env.DEVORY_REMOTE_FACTORY_URL = "https://factory.example.com";
84
+ assert.equal(resolveFactoryMode(), "hosted");
85
+ });
86
+ });
87
+
88
+ describe("resolveFactoryEnvironment", () => {
89
+ test("returns root, mode, and derived paths together", () => {
90
+ fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
91
+ process.env.DEVORY_FACTORY_MODE = "hosted";
92
+
93
+ const result = resolveFactoryEnvironment(tmpDir);
94
+ assert.equal(result.root, tmpDir);
95
+ assert.equal(result.source, "git-walk");
96
+ assert.equal(result.mode, "hosted");
97
+ assert.deepEqual(result.paths, factoryPaths(tmpDir));
98
+ });
99
+ });
@@ -0,0 +1,103 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export type FactoryRootSource =
5
+ | "env:DEVORY_FACTORY_ROOT"
6
+ | "env:FACTORY_ROOT"
7
+ | "git-walk"
8
+ | "cwd";
9
+
10
+ export type FactoryMode = "local" | "hosted";
11
+
12
+ export interface FactoryPaths {
13
+ tasksDir: string;
14
+ runsDir: string;
15
+ artifactsDir: string;
16
+ contextFile: string;
17
+ }
18
+
19
+ export interface FactoryEnvironment {
20
+ root: string;
21
+ source: FactoryRootSource;
22
+ mode: FactoryMode;
23
+ paths: FactoryPaths;
24
+ }
25
+
26
+ const FACTORY_MARKER = "FACTORY_CONTEXT.md";
27
+
28
+ function trimEnv(value: string | undefined): string | null {
29
+ if (typeof value !== "string") return null;
30
+ const trimmed = value.trim();
31
+ return trimmed === "" ? null : trimmed;
32
+ }
33
+
34
+ export function findFactoryContextDir(startDir: string): string | null {
35
+ let current = path.resolve(startDir);
36
+
37
+ while (true) {
38
+ if (fs.existsSync(path.join(current, FACTORY_MARKER))) {
39
+ return current;
40
+ }
41
+ const parent = path.dirname(current);
42
+ if (parent === current) {
43
+ return null;
44
+ }
45
+ current = parent;
46
+ }
47
+ }
48
+
49
+ export function resolveFactoryRoot(startDir = process.cwd()): {
50
+ root: string;
51
+ source: FactoryRootSource;
52
+ } {
53
+ const explicit = trimEnv(process.env.DEVORY_FACTORY_ROOT);
54
+ if (explicit) {
55
+ return { root: explicit, source: "env:DEVORY_FACTORY_ROOT" };
56
+ }
57
+
58
+ const legacy = trimEnv(process.env.FACTORY_ROOT);
59
+ if (legacy) {
60
+ return { root: legacy, source: "env:FACTORY_ROOT" };
61
+ }
62
+
63
+ const walked = findFactoryContextDir(startDir);
64
+ if (walked) {
65
+ return { root: walked, source: "git-walk" };
66
+ }
67
+
68
+ return { root: path.resolve(startDir), source: "cwd" };
69
+ }
70
+
71
+ export function factoryPaths(root: string): FactoryPaths {
72
+ return {
73
+ tasksDir: path.join(root, "tasks"),
74
+ runsDir: path.join(root, "runs"),
75
+ artifactsDir: path.join(root, "artifacts"),
76
+ contextFile: path.join(root, FACTORY_MARKER),
77
+ };
78
+ }
79
+
80
+ export function resolveFactoryMode(env: NodeJS.ProcessEnv = process.env): FactoryMode {
81
+ const explicitMode = trimEnv(env.DEVORY_FACTORY_MODE) ?? trimEnv(env.FACTORY_MODE);
82
+ if (explicitMode === "hosted") return "hosted";
83
+ if (explicitMode === "local") return "local";
84
+
85
+ if (trimEnv(env.DEVORY_REMOTE_FACTORY_URL) || trimEnv(env.FACTORY_REMOTE_URL)) {
86
+ return "hosted";
87
+ }
88
+
89
+ return "local";
90
+ }
91
+
92
+ export function resolveFactoryEnvironment(
93
+ startDir = process.cwd(),
94
+ env: NodeJS.ProcessEnv = process.env
95
+ ): FactoryEnvironment {
96
+ const { root, source } = resolveFactoryRoot(startDir);
97
+ return {
98
+ root,
99
+ source,
100
+ mode: resolveFactoryMode(env),
101
+ paths: factoryPaths(root),
102
+ };
103
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @devory/core — public API
3
+ *
4
+ * Shared types, parsing utilities, and path configuration
5
+ * for the AI Dev Factory monorepo.
6
+ */
7
+
8
+ export { parseFrontmatter } from "./parse.ts";
9
+ export type { TaskMeta, ParseResult } from "./parse.ts";
10
+ export {
11
+ factoryPaths,
12
+ findFactoryContextDir,
13
+ resolveFactoryEnvironment,
14
+ resolveFactoryMode,
15
+ resolveFactoryRoot,
16
+ } from "./factory-environment.ts";
17
+ export type {
18
+ FactoryEnvironment,
19
+ FactoryMode,
20
+ FactoryPaths,
21
+ FactoryRootSource,
22
+ } from "./factory-environment.ts";
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @devory/core — parseFrontmatter tests.
3
+ *
4
+ * Run from factory root: tsx --test packages/core/src/parse.test.ts
5
+ */
6
+
7
+ import { test, describe } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { parseFrontmatter } from "./parse.js";
10
+
11
+ // ── Basic parsing ─────────────────────────────────────────────────────────────
12
+
13
+ describe("parseFrontmatter", () => {
14
+ test("parses a minimal frontmatter block", () => {
15
+ const content = `---
16
+ id: factory-001
17
+ title: My Task
18
+ status: backlog
19
+ ---
20
+ Body text here.`;
21
+ const { meta, body } = parseFrontmatter(content);
22
+ assert.equal(meta.id, "factory-001");
23
+ assert.equal(meta.title, "My Task");
24
+ assert.equal(meta.status, "backlog");
25
+ assert.ok(body.includes("Body text here."));
26
+ });
27
+
28
+ test("returns empty meta and full content when no frontmatter delimiter", () => {
29
+ const content = "No frontmatter here.";
30
+ const { meta, body } = parseFrontmatter(content);
31
+ assert.deepEqual(meta, {});
32
+ assert.equal(body, content);
33
+ });
34
+
35
+ test("returns empty meta when opening delimiter missing", () => {
36
+ const content = "id: factory-001\n---\nbody";
37
+ const { meta } = parseFrontmatter(content);
38
+ assert.deepEqual(meta, {});
39
+ });
40
+
41
+ test("returns empty meta when closing delimiter missing", () => {
42
+ const content = "---\nid: factory-001\nbody without close";
43
+ const { meta } = parseFrontmatter(content);
44
+ assert.deepEqual(meta, {});
45
+ });
46
+
47
+ test("parses string arrays from list items", () => {
48
+ const content = `---
49
+ depends_on:
50
+ - factory-001
51
+ - factory-002
52
+ ---
53
+ `;
54
+ const { meta } = parseFrontmatter(content);
55
+ assert.deepEqual(meta.depends_on, ["factory-001", "factory-002"]);
56
+ });
57
+
58
+ test("parses empty array from empty value", () => {
59
+ const content = `---
60
+ depends_on:
61
+ ---
62
+ `;
63
+ const { meta } = parseFrontmatter(content);
64
+ assert.deepEqual(meta.depends_on, []);
65
+ });
66
+
67
+ test("parses empty array from [] syntax", () => {
68
+ const content = `---
69
+ depends_on: []
70
+ ---
71
+ `;
72
+ const { meta } = parseFrontmatter(content);
73
+ assert.deepEqual(meta.depends_on, []);
74
+ });
75
+
76
+ test("body does not include frontmatter", () => {
77
+ const content = `---
78
+ id: factory-001
79
+ ---
80
+ ## Section
81
+ Content here.`;
82
+ const { body } = parseFrontmatter(content);
83
+ assert.ok(!body.includes("id: factory-001"));
84
+ assert.ok(body.includes("## Section"));
85
+ });
86
+
87
+ test("parses all standard task fields", () => {
88
+ const content = `---
89
+ id: factory-010
90
+ title: Test Task
91
+ project: ai-dev-factory
92
+ repo: .
93
+ branch: task/factory-010
94
+ type: feature
95
+ priority: high
96
+ status: ready
97
+ agent: fullstack-builder
98
+ ---
99
+ `;
100
+ const { meta } = parseFrontmatter(content);
101
+ assert.equal(meta.id, "factory-010");
102
+ assert.equal(meta.title, "Test Task");
103
+ assert.equal(meta.project, "ai-dev-factory");
104
+ assert.equal(meta.type, "feature");
105
+ assert.equal(meta.priority, "high");
106
+ assert.equal(meta.agent, "fullstack-builder");
107
+ });
108
+
109
+ test("parses verification list", () => {
110
+ const content = `---
111
+ verification:
112
+ - npm run test
113
+ - npm run build
114
+ ---
115
+ `;
116
+ const { meta } = parseFrontmatter(content);
117
+ assert.deepEqual(meta.verification, ["npm run test", "npm run build"]);
118
+ });
119
+
120
+ test("handles hyphen-containing keys like depends_on", () => {
121
+ const content = `---
122
+ bundle-id: epic-auth
123
+ ---
124
+ `;
125
+ const { meta } = parseFrontmatter(content);
126
+ assert.equal(meta["bundle-id"], "epic-auth");
127
+ });
128
+
129
+ test("trims whitespace from scalar values", () => {
130
+ const content = `---
131
+ title: My Task
132
+ status: backlog
133
+ ---
134
+ `;
135
+ const { meta } = parseFrontmatter(content);
136
+ assert.equal(meta.title, "My Task");
137
+ assert.equal(meta.status, "backlog");
138
+ });
139
+
140
+ test("ignores list items before any key is set", () => {
141
+ const content = `---
142
+ - orphaned-item
143
+ id: factory-001
144
+ ---
145
+ `;
146
+ const { meta } = parseFrontmatter(content);
147
+ assert.equal(meta.id, "factory-001");
148
+ });
149
+
150
+ test("empty content returns empty meta and empty body", () => {
151
+ const { meta, body } = parseFrontmatter("");
152
+ assert.deepEqual(meta, {});
153
+ assert.equal(body, "");
154
+ });
155
+
156
+ test("content with only delimiter lines returns empty meta", () => {
157
+ const content = "---\n---\n";
158
+ const { meta } = parseFrontmatter(content);
159
+ assert.deepEqual(meta, {});
160
+ });
161
+ });
package/src/parse.ts ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @devory/core — shared frontmatter parsing utilities.
3
+ *
4
+ * Pure functions with no external dependencies.
5
+ * Used by workers/lib, scripts, and apps/devory.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export interface TaskMeta {
13
+ id: string;
14
+ title: string;
15
+ project: string;
16
+ repo: string;
17
+ branch: string;
18
+ type: string;
19
+ priority: string;
20
+ status: string;
21
+ agent: string;
22
+ /** Simulated execution outcome written by an agent after doing work. */
23
+ execution_result?: string;
24
+ depends_on: string[];
25
+ files_likely_affected: string[];
26
+ verification: string[];
27
+
28
+ // Planner / parent-task fields (all optional)
29
+ planner?: boolean;
30
+ parent_task?: string;
31
+ lane?: string;
32
+ repo_area?: string;
33
+ decomposition_hint?: string;
34
+ required_capabilities?: string[];
35
+ preferred_capabilities?: string[];
36
+ disallowed_models?: string[];
37
+ preferred_models?: string[];
38
+ execution_profile?: string;
39
+ pipeline?: string;
40
+ context_intensity?: string;
41
+ quality_priority?: string;
42
+ speed_priority?: string;
43
+ max_cost_tier?: string;
44
+
45
+ // Bundle / epic fields (all optional)
46
+ bundle_id?: string;
47
+ bundle_title?: string;
48
+ bundle_phase?: string;
49
+
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ export interface ParseResult {
54
+ meta: Partial<TaskMeta>;
55
+ body: string;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Frontmatter parser
60
+ // Handles simple YAML: scalar strings and flat string arrays ("- item").
61
+ // Does not depend on any external package.
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export function parseFrontmatter(content: string): ParseResult {
65
+ const lines = content.split("\n");
66
+
67
+ if (lines[0]?.trim() !== "---") {
68
+ return { meta: {}, body: content };
69
+ }
70
+
71
+ const closeIdx = lines.indexOf("---", 1);
72
+ if (closeIdx === -1) {
73
+ return { meta: {}, body: content };
74
+ }
75
+
76
+ const yamlLines = lines.slice(1, closeIdx);
77
+ const body = lines.slice(closeIdx + 1).join("\n");
78
+ const meta: Partial<TaskMeta> = {};
79
+ let currentKey = "";
80
+
81
+ for (const line of yamlLines) {
82
+ const listMatch = line.match(/^\s+-\s+(.*)/);
83
+ const kvMatch = line.match(/^([\w_][\w_-]*):\s*(.*)/);
84
+
85
+ if (listMatch && currentKey) {
86
+ const arr = meta[currentKey];
87
+ if (Array.isArray(arr)) {
88
+ arr.push(listMatch[1].trim());
89
+ }
90
+ } else if (kvMatch) {
91
+ currentKey = kvMatch[1];
92
+ const val = kvMatch[2].trim();
93
+
94
+ if (val === "" || val === "[]") {
95
+ (meta as Record<string, unknown>)[currentKey] = [];
96
+ } else {
97
+ (meta as Record<string, unknown>)[currentKey] = val;
98
+ }
99
+ }
100
+ }
101
+
102
+ return { meta, body };
103
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = {};