chapterhouse 0.3.13 → 0.3.15
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 +2 -69
- package/dist/api/server.js +8 -155
- package/dist/api/server.test.js +1 -1
- package/dist/cli.js +0 -30
- package/dist/config.js +11 -3
- package/dist/copilot/agent-event-bus.js +41 -0
- package/dist/copilot/agent-event-bus.test.js +23 -0
- package/dist/copilot/agents.js +4 -59
- package/dist/copilot/orchestrator.js +20 -39
- package/dist/copilot/orchestrator.test.js +73 -158
- package/dist/copilot/system-message.js +7 -0
- package/dist/copilot/task-event-log.js +5 -5
- package/dist/copilot/task-event-log.test.js +68 -142
- package/dist/copilot/tools.js +72 -132
- package/dist/daemon.js +6 -22
- package/dist/store/db.js +2 -50
- package/dist/store/db.test.js +0 -45
- package/dist/wiki/fs.js +5 -0
- package/dist/wiki/index-manager.js +92 -17
- package/dist/wiki/index-manager.test.js +19 -0
- package/dist/wiki/migrate-topics.js +132 -0
- package/dist/wiki/migrate-topics.test.js +57 -0
- package/dist/wiki/topic-structure.js +167 -0
- package/dist/wiki/topic-structure.test.js +74 -0
- package/package.json +1 -3
- package/web/dist/assets/index-BlIWCM11.js +217 -0
- package/web/dist/assets/index-BlIWCM11.js.map +1 -0
- package/web/dist/assets/{index-BtAcw3EP.css → index-lvHFM_ut.css} +1 -1
- package/web/dist/index.html +2 -2
- package/dist/api/ralph.js +0 -153
- package/dist/api/ralph.test.js +0 -101
- package/dist/copilot/agents.squad.test.js +0 -72
- package/dist/copilot/hooks.js +0 -157
- package/dist/copilot/hooks.test.js +0 -315
- package/dist/copilot/squad-event-bus.js +0 -27
- package/dist/copilot/tools.squad.test.js +0 -168
- package/dist/squad/charter.js +0 -125
- package/dist/squad/charter.test.js +0 -89
- package/dist/squad/context.js +0 -48
- package/dist/squad/context.test.js +0 -59
- package/dist/squad/discovery.js +0 -268
- package/dist/squad/discovery.test.js +0 -154
- package/dist/squad/index.js +0 -9
- package/dist/squad/init-cli.js +0 -109
- package/dist/squad/init.js +0 -395
- package/dist/squad/init.test.js +0 -351
- package/dist/squad/mirror.js +0 -83
- package/dist/squad/mirror.scheduler.js +0 -80
- package/dist/squad/mirror.scheduler.test.js +0 -197
- package/dist/squad/mirror.test.js +0 -172
- package/dist/squad/registry.js +0 -162
- package/dist/squad/registry.test.js +0 -31
- package/dist/squad/squad-coordinator-system-message.test.js +0 -190
- package/dist/squad/squad-session-routing.test.js +0 -260
- package/dist/squad/types.js +0 -4
- package/dist/squad/worktree.js +0 -295
- package/dist/squad/worktree.test.js +0 -189
- package/dist/store/squad-sessions.test.js +0 -341
- package/web/dist/assets/index-IgSOXx_a.js +0 -219
- package/web/dist/assets/index-IgSOXx_a.js.map +0 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Wiki topic structure: the canonical layout rules for pages/.
|
|
3
|
+
//
|
|
4
|
+
// Entity categories (projects, people, orgs, tools, topics, areas by default)
|
|
5
|
+
// require a topic-level directory: pages/<category>/<topic-slug>/<page>.md,
|
|
6
|
+
// where index.md is the topic's overview page and other files are facets.
|
|
7
|
+
//
|
|
8
|
+
// Flat categories (preferences, facts, routines, decisions) stay as single
|
|
9
|
+
// files: pages/<category>.md.
|
|
10
|
+
//
|
|
11
|
+
// Some prefixes (conversations/, team/, okrs/, kpis/, shared/) follow their
|
|
12
|
+
// own conventions and are exempt from these rules.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
import { config } from "../config.js";
|
|
15
|
+
import { normalizeWikiPath } from "./path-utils.js";
|
|
16
|
+
/**
|
|
17
|
+
* Flat categories: each is a single file at pages/<category>.md.
|
|
18
|
+
* Note: `decisions` is intentionally NOT flat — a decision is always recorded as a
|
|
19
|
+
* `decisions.md` facet under the entity it concerns (e.g. pages/projects/chapterhouse/decisions.md).
|
|
20
|
+
*/
|
|
21
|
+
export const FLAT_CATEGORIES = ["preferences", "facts", "routines"];
|
|
22
|
+
/** Path prefixes (relative to the wiki root) that follow their own conventions. */
|
|
23
|
+
export const EXEMPT_PREFIXES = [
|
|
24
|
+
"pages/conversations/",
|
|
25
|
+
"pages/team/",
|
|
26
|
+
"pages/okrs/",
|
|
27
|
+
"pages/kpis/",
|
|
28
|
+
"pages/shared/",
|
|
29
|
+
];
|
|
30
|
+
/** Map a `remember`-style category name to its directory under pages/. */
|
|
31
|
+
const CATEGORY_DIR_MAP = {
|
|
32
|
+
person: "people",
|
|
33
|
+
people: "people",
|
|
34
|
+
project: "projects",
|
|
35
|
+
projects: "projects",
|
|
36
|
+
org: "orgs",
|
|
37
|
+
orgs: "orgs",
|
|
38
|
+
organization: "orgs",
|
|
39
|
+
tool: "tools",
|
|
40
|
+
tools: "tools",
|
|
41
|
+
topic: "topics",
|
|
42
|
+
topics: "topics",
|
|
43
|
+
area: "areas",
|
|
44
|
+
areas: "areas",
|
|
45
|
+
preference: "preferences",
|
|
46
|
+
preferences: "preferences",
|
|
47
|
+
fact: "facts",
|
|
48
|
+
facts: "facts",
|
|
49
|
+
routine: "routines",
|
|
50
|
+
routines: "routines",
|
|
51
|
+
decision: "decisions",
|
|
52
|
+
decisions: "decisions",
|
|
53
|
+
};
|
|
54
|
+
export function getCategoryDir(category) {
|
|
55
|
+
return CATEGORY_DIR_MAP[category.toLowerCase()] || category.toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
/** The active list of entity-category directories (configurable via WIKI_ENTITY_CATEGORIES). */
|
|
58
|
+
export function entityCategories() {
|
|
59
|
+
return config.wikiEntityCategories;
|
|
60
|
+
}
|
|
61
|
+
function isEntityCategory(dir) {
|
|
62
|
+
return entityCategories().includes(dir);
|
|
63
|
+
}
|
|
64
|
+
function isFlatCategory(dir) {
|
|
65
|
+
return FLAT_CATEGORIES.includes(dir);
|
|
66
|
+
}
|
|
67
|
+
function isExemptPath(relativePath) {
|
|
68
|
+
return EXEMPT_PREFIXES.some((p) => relativePath.startsWith(p));
|
|
69
|
+
}
|
|
70
|
+
/** Slugify a free-text name into a wiki-safe path segment. */
|
|
71
|
+
export function slugify(name) {
|
|
72
|
+
return name
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
75
|
+
.replace(/^-+|-+$/g, "")
|
|
76
|
+
|| "untitled";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Canonical path for an entity topic page.
|
|
80
|
+
* topicPagePath("project", "Chapterhouse") -> "pages/projects/chapterhouse/index.md"
|
|
81
|
+
* topicPagePath("project", "Chapterhouse", "decisions") -> "pages/projects/chapterhouse/decisions.md"
|
|
82
|
+
*/
|
|
83
|
+
export function topicPagePath(category, entity, facet = "index") {
|
|
84
|
+
const dir = getCategoryDir(category);
|
|
85
|
+
const topic = slugify(entity);
|
|
86
|
+
const page = slugify(facet) || "index";
|
|
87
|
+
return `pages/${dir}/${topic}/${page}.md`;
|
|
88
|
+
}
|
|
89
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
90
|
+
/**
|
|
91
|
+
* Validate that a tool-writable wiki page path conforms to the canonical layout.
|
|
92
|
+
* Assumes the path has already passed the basic safety checks in assertPagePath
|
|
93
|
+
* (under pages/, ends in .md, no traversal).
|
|
94
|
+
*/
|
|
95
|
+
export function validateTopicPath(relativePath) {
|
|
96
|
+
const path = normalizeWikiPath(relativePath);
|
|
97
|
+
if (!path.startsWith("pages/")) {
|
|
98
|
+
// assertPagePath handles this; nothing topic-structural to add.
|
|
99
|
+
return { ok: true };
|
|
100
|
+
}
|
|
101
|
+
if (isExemptPath(path)) {
|
|
102
|
+
return { ok: true };
|
|
103
|
+
}
|
|
104
|
+
const rest = path.slice("pages/".length); // e.g. "projects/chapterhouse/index.md"
|
|
105
|
+
const segments = rest.split("/").filter(Boolean);
|
|
106
|
+
// For a single-segment path the "category" is the bare filename (pages/preferences.md).
|
|
107
|
+
const topDir = segments.length === 1 ? segments[0].replace(/\.md$/i, "") : (segments[0] ?? "");
|
|
108
|
+
if (isFlatCategory(topDir)) {
|
|
109
|
+
if (segments.length === 1 && rest === `${topDir}.md`) {
|
|
110
|
+
return { ok: true };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
reason: `'${topDir}' is a flat category and must be a single file.`,
|
|
115
|
+
suggestion: `pages/${topDir}.md`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (isEntityCategory(topDir)) {
|
|
119
|
+
if (segments.length === 2) {
|
|
120
|
+
// pages/projects/foo.md -> pages/projects/foo/index.md
|
|
121
|
+
const base = slugify(segments[1].replace(/\.md$/i, ""));
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
reason: `Entity category '${topDir}' requires a topic directory: pages/${topDir}/<topic>/<page>.md. ` +
|
|
125
|
+
`'index.md' is the topic's overview page.`,
|
|
126
|
+
suggestion: `pages/${topDir}/${base}/index.md`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (segments.length !== 3) {
|
|
130
|
+
const flat = segments.slice(1, -1).map((s) => slugify(s.replace(/\.md$/i, ""))).filter(Boolean);
|
|
131
|
+
const topic = flat[0] || "topic";
|
|
132
|
+
const page = slugify(segments[segments.length - 1].replace(/\.md$/i, "")) || "index";
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
reason: `Entity category '${topDir}' allows exactly one topic level: pages/${topDir}/<topic>/<page>.md. ` +
|
|
136
|
+
`Got ${segments.length} path segments.`,
|
|
137
|
+
suggestion: `pages/${topDir}/${topic}/${page}.md`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const [, topic, file] = segments;
|
|
141
|
+
const pageSlug = file.replace(/\.md$/i, "");
|
|
142
|
+
if (!SLUG_RE.test(topic) || !SLUG_RE.test(pageSlug)) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
reason: `Topic and page names must be lowercase slugs ([a-z0-9-]).`,
|
|
146
|
+
suggestion: `pages/${topDir}/${slugify(topic)}/${slugify(pageSlug) || "index"}.md`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { ok: true };
|
|
150
|
+
}
|
|
151
|
+
// Unknown top-level directory.
|
|
152
|
+
const valid = [...entityCategories(), ...FLAT_CATEGORIES].sort().join(", ");
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: `'${topDir}' is not a recognized wiki category. ` +
|
|
156
|
+
`Use one of: ${valid} (entity categories take a topic directory: pages/<category>/<topic>/<page>.md). ` +
|
|
157
|
+
`Conversations and team pages are written by the system.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** Convenience: returns the error message string for an invalid path, or undefined if valid. */
|
|
161
|
+
export function topicPathError(relativePath) {
|
|
162
|
+
const result = validateTopicPath(relativePath);
|
|
163
|
+
if (result.ok)
|
|
164
|
+
return undefined;
|
|
165
|
+
return result.suggestion ? `${result.reason} Use: ${result.suggestion}` : result.reason;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=topic-structure.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { getCategoryDir, slugify, topicPagePath, validateTopicPath, topicPathError, } from "./topic-structure.js";
|
|
4
|
+
test("getCategoryDir maps category names to directories", () => {
|
|
5
|
+
assert.equal(getCategoryDir("project"), "projects");
|
|
6
|
+
assert.equal(getCategoryDir("projects"), "projects");
|
|
7
|
+
assert.equal(getCategoryDir("person"), "people");
|
|
8
|
+
assert.equal(getCategoryDir("org"), "orgs");
|
|
9
|
+
assert.equal(getCategoryDir("tool"), "tools");
|
|
10
|
+
assert.equal(getCategoryDir("topic"), "topics");
|
|
11
|
+
assert.equal(getCategoryDir("area"), "areas");
|
|
12
|
+
assert.equal(getCategoryDir("decision"), "decisions");
|
|
13
|
+
assert.equal(getCategoryDir("preference"), "preferences");
|
|
14
|
+
});
|
|
15
|
+
test("slugify normalizes free text into a safe path segment", () => {
|
|
16
|
+
assert.equal(slugify("Chapter House!"), "chapter-house");
|
|
17
|
+
assert.equal(slugify(" --Foo_Bar-- "), "foo-bar");
|
|
18
|
+
assert.equal(slugify("v0.3.14"), "v0-3-14");
|
|
19
|
+
assert.equal(slugify("!!!"), "untitled");
|
|
20
|
+
});
|
|
21
|
+
test("topicPagePath builds canonical paths", () => {
|
|
22
|
+
assert.equal(topicPagePath("project", "Chapterhouse"), "pages/projects/chapterhouse/index.md");
|
|
23
|
+
assert.equal(topicPagePath("project", "Chapterhouse", "decisions"), "pages/projects/chapterhouse/decisions.md");
|
|
24
|
+
assert.equal(topicPagePath("person", "Brian K"), "pages/people/brian-k/index.md");
|
|
25
|
+
assert.equal(topicPagePath("tool", "Kubernetes"), "pages/tools/kubernetes/index.md");
|
|
26
|
+
});
|
|
27
|
+
test("validateTopicPath accepts canonical and exempt paths", () => {
|
|
28
|
+
assert.deepEqual(validateTopicPath("pages/projects/chapterhouse/index.md"), { ok: true });
|
|
29
|
+
assert.deepEqual(validateTopicPath("pages/projects/chapterhouse/decisions.md"), { ok: true });
|
|
30
|
+
assert.deepEqual(validateTopicPath("pages/people/brian/index.md"), { ok: true });
|
|
31
|
+
assert.deepEqual(validateTopicPath("pages/preferences.md"), { ok: true });
|
|
32
|
+
assert.deepEqual(validateTopicPath("pages/facts.md"), { ok: true });
|
|
33
|
+
assert.deepEqual(validateTopicPath("pages/conversations/2026-05-12.md"), { ok: true });
|
|
34
|
+
assert.deepEqual(validateTopicPath("pages/team/onboarding.md"), { ok: true });
|
|
35
|
+
assert.deepEqual(validateTopicPath("pages/okrs/2026-Q2.md"), { ok: true });
|
|
36
|
+
});
|
|
37
|
+
test("validateTopicPath rejects a bare entity-category file and suggests the topic index", () => {
|
|
38
|
+
const r = validateTopicPath("pages/projects/chapterhouse.md");
|
|
39
|
+
assert.equal(r.ok, false);
|
|
40
|
+
assert.equal(r.ok === false && r.suggestion, "pages/projects/chapterhouse/index.md");
|
|
41
|
+
});
|
|
42
|
+
test("validateTopicPath rejects entity paths that nest too deep", () => {
|
|
43
|
+
const r = validateTopicPath("pages/projects/chapterhouse/sub/deep.md");
|
|
44
|
+
assert.equal(r.ok, false);
|
|
45
|
+
assert.equal(r.ok === false && r.suggestion, "pages/projects/chapterhouse/deep.md");
|
|
46
|
+
});
|
|
47
|
+
test("validateTopicPath rejects a flat category used as a directory", () => {
|
|
48
|
+
const r = validateTopicPath("pages/preferences/foo.md");
|
|
49
|
+
assert.equal(r.ok, false);
|
|
50
|
+
assert.equal(r.ok === false && r.suggestion, "pages/preferences.md");
|
|
51
|
+
});
|
|
52
|
+
test("validateTopicPath treats `decisions` as an entity facet, not a flat page", () => {
|
|
53
|
+
// A standalone pages/decisions.md is no longer valid…
|
|
54
|
+
assert.equal(validateTopicPath("pages/decisions.md").ok, false);
|
|
55
|
+
// …decisions live as a facet under the entity they concern.
|
|
56
|
+
assert.deepEqual(validateTopicPath("pages/projects/chapterhouse/decisions.md"), { ok: true });
|
|
57
|
+
assert.deepEqual(validateTopicPath("pages/tools/kubernetes/decisions.md"), { ok: true });
|
|
58
|
+
});
|
|
59
|
+
test("validateTopicPath rejects unknown top-level categories", () => {
|
|
60
|
+
const r = validateTopicPath("pages/randomstuff/foo.md");
|
|
61
|
+
assert.equal(r.ok, false);
|
|
62
|
+
assert.match(r.ok === false ? r.reason : "", /not a recognized wiki category/);
|
|
63
|
+
});
|
|
64
|
+
test("validateTopicPath rejects non-slug topic or page names", () => {
|
|
65
|
+
const r = validateTopicPath("pages/projects/Chapter House/index.md");
|
|
66
|
+
assert.equal(r.ok, false);
|
|
67
|
+
assert.equal(r.ok === false && r.suggestion, "pages/projects/chapter-house/index.md");
|
|
68
|
+
});
|
|
69
|
+
test("topicPathError returns a message for invalid paths and undefined for valid ones", () => {
|
|
70
|
+
assert.equal(topicPathError("pages/projects/chapterhouse/index.md"), undefined);
|
|
71
|
+
assert.equal(topicPathError("pages/preferences.md"), undefined);
|
|
72
|
+
assert.match(String(topicPathError("pages/projects/chapterhouse.md")), /Use: pages\/projects\/chapterhouse\/index\.md/);
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=topic-structure.test.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chapterhouse",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "Chapterhouse — a team-level AI assistant for engineering teams, built on the GitHub Copilot SDK. Web UI only.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"chapterhouse": "dist/cli.js"
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
},
|
|
52
52
|
"type": "module",
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@bradygaster/squad-sdk": "0.9.4",
|
|
55
54
|
"@github/copilot-sdk": "^0.3.0",
|
|
56
55
|
"azure-devops-node-api": "^15.1.2",
|
|
57
56
|
"better-sqlite3": "^12.6.2",
|
|
@@ -65,7 +64,6 @@
|
|
|
65
64
|
"zod": "^4.3.6"
|
|
66
65
|
},
|
|
67
66
|
"devDependencies": {
|
|
68
|
-
"@bradygaster/squad-cli": "^0.9.4",
|
|
69
67
|
"@commitlint/cli": "^21.0.0",
|
|
70
68
|
"@commitlint/config-conventional": "^21.0.0",
|
|
71
69
|
"@types/better-sqlite3": "^7.6.13",
|