@g3nomaly/g3n-skills 0.1.3 → 0.1.4
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/.claude-plugin/marketplace.json +4 -4
- package/README.md +1 -1
- package/package.json +1 -1
- package/skills/coding/.claude-plugin/plugin.json +2 -2
- package/skills/coding/vocabulary/SKILL.md +47 -0
- package/skills/coding/vocabulary/agents/openai.yaml +5 -0
- package/skills/task-manager/.claude-plugin/plugin.json +1 -2
- package/skills/task-manager/task-manager/hooks/get-entry.ts +4 -4
- package/skills/task-manager/task-manager/hooks/guard-tree.ts +2 -2
- package/skills/task-manager/task-manager/hooks/next-entry.ts +3 -3
- package/skills/task-manager/task-manager/references/hooks.md +1 -1
- package/skills/task-manager/task-manager/references/tree.md +0 -1
- package/skills/task-manager/task-manager/src/hooks/cli.ts +11 -1
- package/skills/task-manager/task-manager/src/hooks/index.ts +1 -1
- package/skills/task-manager/task-manager/src/schemas/index.ts +2 -2
- package/skills/task-manager/task-manager/src/schemas/term.ts +9 -22
- package/skills/task-manager/task-manager/src/vocabulary/file.ts +40 -84
- package/skills/task-manager/task-manager/src/vocabulary/index.ts +3 -3
- package/skills/task-manager/task-manager/src/vocabulary/match.ts +3 -34
- package/skills/task-manager/vocabulary/SKILL.md +0 -42
- package/skills/task-manager/vocabulary/agents/openai.yaml +0 -5
- package/skills/task-manager/vocabulary/hooks/get-term.ts +0 -18
- package/skills/task-manager/vocabulary/hooks/list-terms.ts +0 -12
- package/skills/task-manager/vocabulary/hooks/remove-term.ts +0 -22
- package/skills/task-manager/vocabulary/hooks/set-term.ts +0 -37
- package/skills/task-manager/vocabulary/references/usage.md +0 -26
- package/skills/task-manager/vocabulary/tsconfig.json +0 -7
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
{
|
|
18
18
|
"name": "coding",
|
|
19
19
|
"source": "./skills/coding",
|
|
20
|
-
"description": "How to write, check and document code, per language,
|
|
20
|
+
"description": "How to write, check and document code, per language, query a repository with analyze, and keep its glossary.",
|
|
21
21
|
"skills": [
|
|
22
22
|
"./code",
|
|
23
23
|
"./document",
|
|
24
|
-
"./analyze"
|
|
24
|
+
"./analyze",
|
|
25
|
+
"./vocabulary"
|
|
25
26
|
]
|
|
26
27
|
},
|
|
27
28
|
{
|
|
@@ -49,10 +50,9 @@
|
|
|
49
50
|
{
|
|
50
51
|
"name": "task-manager",
|
|
51
52
|
"source": "./skills/task-manager",
|
|
52
|
-
"description": "Query and drive a tree of plans, phases and tasks through hooks, with the plan's decisions
|
|
53
|
+
"description": "Query and drive a tree of plans, phases and tasks through hooks, with the plan's decisions kept beside them.",
|
|
53
54
|
"skills": [
|
|
54
55
|
"./task-manager",
|
|
55
|
-
"./vocabulary",
|
|
56
56
|
"./decisions"
|
|
57
57
|
]
|
|
58
58
|
},
|
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ Skills marked _manual_ run only when you type their command, such as `/grill-me`
|
|
|
54
54
|
| `coding` | `code` | Rules for writing and checking code, with a toolchain reference per language |
|
|
55
55
|
| `coding` | `document` | Docstrings and comments for functions and classes, and where the reasons behind code go |
|
|
56
56
|
| `coding` | `analyze` | Query a repository's model with `g3n-analyze` and rebuild its documentation site in `analyze_out/` |
|
|
57
|
+
| `coding` | `vocabulary` | One word per concept, recorded in `docs/glossary.md`, so names don't drift between sessions |
|
|
57
58
|
| `grilling` | `grilling` | Interview the user relentlessly to stress-test a plan, decision or idea |
|
|
58
59
|
| `grilling` | `grill-me` | _Manual._ Starts a `/grilling` session |
|
|
59
60
|
| `grilling` | `grill-with-docs` | _Manual._ A grilling session that writes ADRs and a glossary as it goes |
|
|
@@ -63,7 +64,6 @@ Skills marked _manual_ run only when you type their command, such as `/grill-me`
|
|
|
63
64
|
| `speech` | `plain-language` | Write text readers can find, understand and use on the first read, following ISO 24495-1 |
|
|
64
65
|
| `speech` | `no-questions` | Replies end without questions or offers; a Stop hook refuses a reply that asks one |
|
|
65
66
|
| `task-manager` | `task-manager` | Query plans, phases and tasks and change their status through hooks |
|
|
66
|
-
| `task-manager` | `vocabulary` | One word per concept, stored in the task-manager tree, so names don't drift between sessions |
|
|
67
67
|
| `task-manager` | `decisions` | Record and look up a plan's decisions as numbered files inside the plan |
|
|
68
68
|
| `using-hooks` | `hook-pre-tool-use` | _Manual._ Writing a `PreToolUse` hook, which can allow, deny or escalate a tool call |
|
|
69
69
|
| `using-hooks` | `hook-post-tool-use` | _Manual._ Writing a `PostToolUse` hook, which reacts to a finished tool call |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coding",
|
|
3
|
-
"description": "How to write, check and document code, per language,
|
|
3
|
+
"description": "How to write, check and document code, per language, query a repository with analyze, and keep its glossary.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"skills": ["./code", "./document", "./analyze"]
|
|
6
|
+
"skills": ["./code", "./document", "./analyze", "./vocabulary"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vocabulary
|
|
3
|
+
description: Use when about to name a domain concept in code, a task or a doc, when the user corrects a word, when two words are in use for one thing, or when another skill needs a term looked up or recorded.
|
|
4
|
+
registryDependencies: [domain-modeling]
|
|
5
|
+
disable-model-invocation: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Every session starts without the last one's words, so names _drift_: the _phase_ of Monday is the _stage_ of Tuesday, and by Friday the code has both. The glossary is the one list that stops it. Each **term** carries a definition and the **avoided words** it replaces.
|
|
9
|
+
|
|
10
|
+
The terms live in `docs/glossary.md`. Read and edit it directly. `g3n-analyze` reads the same file and links every term to its first use in the docs and code, and the task-manager's `get-entry` and `next-entry` list the terms each entry mentions.
|
|
11
|
+
|
|
12
|
+
## Looking a word up
|
|
13
|
+
|
|
14
|
+
Before you name a domain concept, search `docs/glossary.md` for the word.
|
|
15
|
+
|
|
16
|
+
- **Found as a term.** Use it as spelled.
|
|
17
|
+
- **Found under `_Avoid_`.** Use the term it belongs to instead, in prose and in identifiers alike.
|
|
18
|
+
- **Not found.** Use the word, and record it on its second sighting.
|
|
19
|
+
|
|
20
|
+
## Recording a term
|
|
21
|
+
|
|
22
|
+
**Record on the second sighting.** One use of a word is prose. The same concept named twice, or named differently by two people, is a term. A correction from the user is always a term.
|
|
23
|
+
|
|
24
|
+
Create the file when the first term is recorded. Keep this layout, because the parsers depend on it:
|
|
25
|
+
|
|
26
|
+
```md
|
|
27
|
+
# Glossary
|
|
28
|
+
|
|
29
|
+
One sentence on what the project is about.
|
|
30
|
+
|
|
31
|
+
## Planning
|
|
32
|
+
|
|
33
|
+
**Phase**:
|
|
34
|
+
One step of a plan, holding tasks.
|
|
35
|
+
_Avoid_: stage, milestone
|
|
36
|
+
|
|
37
|
+
**Task**:
|
|
38
|
+
One piece of work inside a phase, done in one session.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Term.** A line that starts with the bold name and a colon: `**Phase**:`.
|
|
42
|
+
- **Definition.** One or two sentences on the lines below, saying what the thing _is_. Leave out how it is built.
|
|
43
|
+
- **`_Avoid_:`.** The synonyms actually seen, comma-separated, on the last line of the term. Leave the line out when there are none.
|
|
44
|
+
- **Groups.** A `##` heading above a run of terms groups them. A flat list is fine for a small glossary.
|
|
45
|
+
- **One meaning per word.** A word is a term or an avoided word of one term, never both and never twice. Before adding a word to `_Avoid_`, check that no other term already owns it.
|
|
46
|
+
|
|
47
|
+
A term whose meaning is still contested is sharpened with the `/domain-modeling` skill first, then recorded here.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-manager",
|
|
3
|
-
"description": "Query and drive a tree of plans, phases and tasks through hooks, with the plan's decisions
|
|
3
|
+
"description": "Query and drive a tree of plans, phases and tasks through hooks, with the plan's decisions kept beside them.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"hooks": "./hooks.json",
|
|
7
7
|
"skills": [
|
|
8
8
|
"./task-manager",
|
|
9
|
-
"./vocabulary",
|
|
10
9
|
"./decisions"
|
|
11
10
|
]
|
|
12
11
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/hooks/get-entry.ts
|
|
2
2
|
import { locateEntry, readEntry } from "../src/filetree";
|
|
3
|
-
import {
|
|
4
|
-
import { hasFlag, printJson, requireFlag, resolveRoot, runHook } from "../src/hooks";
|
|
3
|
+
import { readGlossary, termsMentioned } from "../src/vocabulary";
|
|
4
|
+
import { hasFlag, printJson, requireFlag, resolveProject, resolveRoot, runHook } from "../src/hooks";
|
|
5
5
|
|
|
6
|
-
/** Prints the plan, phase or task named by `--id` with its body and the
|
|
6
|
+
/** Prints the plan, phase or task named by `--id` with its body and the glossary terms it mentions. */
|
|
7
7
|
async function run(argv: readonly string[]): Promise<void> {
|
|
8
8
|
const root = await resolveRoot(argv);
|
|
9
9
|
const name = requireFlag(argv, "--id", "auth-rework");
|
|
@@ -19,7 +19,7 @@ async function run(argv: readonly string[]): Promise<void> {
|
|
|
19
19
|
trail: location.trail,
|
|
20
20
|
meta,
|
|
21
21
|
...(hasFlag(argv, "--meta-only") ? {} : { body }),
|
|
22
|
-
vocabulary: termsMentioned(`${meta.title}\n${meta.summary}\n${body}`, await
|
|
22
|
+
vocabulary: termsMentioned(`${meta.title}\n${meta.summary}\n${body}`, await readGlossary(resolveProject(argv))),
|
|
23
23
|
},
|
|
24
24
|
argv,
|
|
25
25
|
);
|
|
@@ -104,8 +104,8 @@ async function run(): Promise<void> {
|
|
|
104
104
|
deny(
|
|
105
105
|
`${target} is inside the task-manager tree, which is read through its hooks. ` +
|
|
106
106
|
"Use get-entry --id <name> for one plan, phase or task in full, next-entry for the " +
|
|
107
|
-
"front of a queue, list-entries --in <name> to see what a folder holds,
|
|
108
|
-
"
|
|
107
|
+
"front of a queue, list-entries --in <name> to see what a folder holds, or list-decisions " +
|
|
108
|
+
"--in <plan> for a plan's decisions.",
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/hooks/next-entry.ts
|
|
2
2
|
import { ENTRY_TYPE, listEntryNames, readEntry, resolveParent } from "../src/filetree";
|
|
3
3
|
import type { TaskStatus } from "../src/schemas";
|
|
4
|
-
import {
|
|
5
|
-
import { hasFlag, printJson, readFlag, resolveRoot, runHook } from "../src/hooks";
|
|
4
|
+
import { readGlossary, termsMentioned } from "../src/vocabulary";
|
|
5
|
+
import { hasFlag, printJson, readFlag, resolveProject, resolveRoot, runHook } from "../src/hooks";
|
|
6
6
|
|
|
7
7
|
const FINISHED: readonly TaskStatus[] = ["completed"];
|
|
8
8
|
const NOT_WORKABLE: readonly TaskStatus[] = ["completed", "blocked"];
|
|
@@ -20,7 +20,7 @@ async function run(argv: readonly string[]): Promise<void> {
|
|
|
20
20
|
const { file, meta, body } = await readEntry(dir, level, name);
|
|
21
21
|
if (skipped.includes(meta.status)) continue;
|
|
22
22
|
|
|
23
|
-
const vocabulary = termsMentioned(`${meta.title}\n${meta.summary}\n${body}`, await
|
|
23
|
+
const vocabulary = termsMentioned(`${meta.title}\n${meta.summary}\n${body}`, await readGlossary(resolveProject(argv)));
|
|
24
24
|
|
|
25
25
|
printJson({ type: ENTRY_TYPE[level], name, file, meta, body, vocabulary }, argv);
|
|
26
26
|
return;
|
|
@@ -16,7 +16,7 @@ JSON on stdout, messages on stderr.
|
|
|
16
16
|
|
|
17
17
|
- `next-entry` and `get-entry` return one entry; none found exits `1`.
|
|
18
18
|
- `list-entries` and `cleanup` return a list; `[]` exits `0`.
|
|
19
|
-
- `get-entry` and `next-entry` attach a `vocabulary` array: the terms the entry mentions.
|
|
19
|
+
- `get-entry` and `next-entry` attach a `vocabulary` array: the terms of `docs/glossary.md` the entry mentions.
|
|
20
20
|
- `set-state`, `cleanup` and `sync-tree` write; the others only read.
|
|
21
21
|
|
|
22
22
|
## Fired by the plugin
|
|
@@ -82,7 +82,17 @@ export async function resolveRoot(argv: readonly string[]): Promise<string> {
|
|
|
82
82
|
const override = readFlag(argv, "--root");
|
|
83
83
|
if (override !== undefined) return resolve(override);
|
|
84
84
|
|
|
85
|
-
return resolveFiletreeRoot(
|
|
85
|
+
return resolveFiletreeRoot(resolveProject(argv));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @brief Resolves the project the hook works in: `--project`, or the working directory.
|
|
90
|
+
*
|
|
91
|
+
* @param argv - Arguments of the hook.
|
|
92
|
+
* @returns Absolute project root.
|
|
93
|
+
*/
|
|
94
|
+
export function resolveProject(argv: readonly string[]): string {
|
|
95
|
+
return resolve(readFlag(argv, "--project") ?? process.cwd());
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
/** Reads everything piped to the hook on stdin, or returns `""` when run from a terminal. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/src/hooks/index.ts
|
|
2
|
-
export { readFlag, requireFlag, hasFlag, oneOf, resolveRoot, readStdin, printJson, runHook } from "./cli";
|
|
2
|
+
export { readFlag, requireFlag, hasFlag, oneOf, resolveRoot, resolveProject, readStdin, printJson, runHook } from "./cli";
|
|
3
3
|
export type { Hook } from "./cli";
|
|
@@ -32,8 +32,8 @@ export {
|
|
|
32
32
|
} from "./decision";
|
|
33
33
|
export type { DecisionStatus, Decision, DecisionIndex } from "./decision";
|
|
34
34
|
|
|
35
|
-
export { TermSchema,
|
|
36
|
-
export type { Term,
|
|
35
|
+
export { TermSchema, GlossarySchema } from "./term";
|
|
36
|
+
export type { Term, Glossary } from "./term";
|
|
37
37
|
|
|
38
38
|
export { CONFIG_FILE, DEFAULT_FILETREE_ROOT, FiletreeConfigSchema } from "./config";
|
|
39
39
|
export type { FiletreeConfig } from "./config";
|
|
@@ -1,38 +1,25 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/src/schemas/term.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
/** One
|
|
5
|
-
const TermNameSchema = z
|
|
6
|
-
.string()
|
|
7
|
-
.regex(/^[^\s#][^\n]*$/, "must be a single line that does not start with # or a space");
|
|
8
|
-
|
|
9
|
-
/** One entry of `vocabulary.md`: the word to use, what it means, and the words it replaces. */
|
|
4
|
+
/** One term of `docs/glossary.md`: the word to use, what it means, and the words it replaces. */
|
|
10
5
|
export const TermSchema = z.strictObject({
|
|
11
|
-
term:
|
|
12
|
-
definition: z
|
|
13
|
-
|
|
14
|
-
.min(1)
|
|
15
|
-
.refine(
|
|
16
|
-
(text) => !text.split("\n").some((line) => line.startsWith("#") || line.startsWith("_Avoid_:")),
|
|
17
|
-
"must not contain a line starting with # or _Avoid_:",
|
|
18
|
-
),
|
|
19
|
-
avoid: z
|
|
20
|
-
.array(TermNameSchema.refine((word) => !word.includes(","), "must not contain a comma"))
|
|
21
|
-
.default([]),
|
|
6
|
+
term: z.string().min(1),
|
|
7
|
+
definition: z.string(),
|
|
8
|
+
avoid: z.array(z.string().min(1)).default([]),
|
|
22
9
|
});
|
|
23
10
|
|
|
24
11
|
/**
|
|
25
|
-
* One
|
|
12
|
+
* One term of `docs/glossary.md`, as `TermSchema` parses it.
|
|
26
13
|
*
|
|
27
14
|
* @property term - The word to use.
|
|
28
|
-
* @property definition - What the term means, as written
|
|
15
|
+
* @property definition - What the term means, as written below its name.
|
|
29
16
|
* @property avoid - Words that mean the same thing and must not be used in its place.
|
|
30
17
|
*/
|
|
31
18
|
export type Term = z.infer<typeof TermSchema>;
|
|
32
19
|
|
|
33
|
-
export const
|
|
20
|
+
export const GlossarySchema = z.strictObject({
|
|
34
21
|
terms: z.array(TermSchema).default([]),
|
|
35
22
|
});
|
|
36
23
|
|
|
37
|
-
/** Every term of `
|
|
38
|
-
export type
|
|
24
|
+
/** Every term of `docs/glossary.md`, as `GlossarySchema` parses it. */
|
|
25
|
+
export type Glossary = z.infer<typeof GlossarySchema>;
|
|
@@ -1,114 +1,70 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/src/vocabulary/file.ts
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { FileValidationError, parseFile } from "../schemas/validation";
|
|
4
|
-
import {
|
|
4
|
+
import { GlossarySchema, type Term } from "../schemas/term";
|
|
5
5
|
|
|
6
|
-
/** File
|
|
7
|
-
export const
|
|
6
|
+
/** File holding every term, relative to the project root. Written by hand, as the `vocabulary` skill describes. */
|
|
7
|
+
export const GLOSSARY_FILE = join("docs", "glossary.md");
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
9
|
+
/** `**Phase**:` or `**Phase:**`, optionally followed by the start of the definition. */
|
|
10
|
+
const TERM_LINE = /^\*\*(.+?):?\*\*:?\s*(.*)$/;
|
|
11
|
+
/** `_Avoid_: stage, milestone` */
|
|
12
|
+
const AVOID_LINE = /^_Avoid_:\s*(.*)$/i;
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* @brief
|
|
15
|
+
* @brief Reads every term of the project's glossary, in the order they sit in the file.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
* @returns Path of `vocabulary.md` inside `root`.
|
|
18
|
-
*/
|
|
19
|
-
export function vocabularyPathFor(root: string): string {
|
|
20
|
-
return join(root, VOCABULARY_FILE);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @brief Reads every term of the tree at `root`, in the order they sit in the file.
|
|
17
|
+
* Text that is not part of a term, such as the title, the intro and the group headings, is skipped.
|
|
25
18
|
*
|
|
26
|
-
* @param
|
|
27
|
-
* @returns Terms in file order, or an empty list when
|
|
28
|
-
* @throws FileValidationError when
|
|
29
|
-
* `writeVocabulary` produces, or names one term twice
|
|
19
|
+
* @param projectRoot - Absolute folder holding `docs/glossary.md`.
|
|
20
|
+
* @returns Terms in file order, or an empty list when the project has no glossary.
|
|
21
|
+
* @throws FileValidationError when one term is defined twice.
|
|
30
22
|
*/
|
|
31
|
-
export async function
|
|
32
|
-
const file =
|
|
23
|
+
export async function readGlossary(projectRoot: string): Promise<Term[]> {
|
|
24
|
+
const file = join(projectRoot, GLOSSARY_FILE);
|
|
33
25
|
if (!(await Bun.file(file).exists())) return [];
|
|
34
26
|
|
|
35
|
-
const terms = parseFile(
|
|
27
|
+
const terms = parseFile(GlossarySchema, file, { terms: parseTerms(await Bun.file(file).text()) }).terms;
|
|
36
28
|
rejectDuplicates(file, terms);
|
|
37
29
|
|
|
38
30
|
return terms;
|
|
39
31
|
}
|
|
40
32
|
|
|
41
33
|
/**
|
|
42
|
-
* @brief
|
|
34
|
+
* @brief Splits the glossary text into raw terms, one per bold term line.
|
|
43
35
|
*
|
|
44
|
-
*
|
|
45
|
-
* @param terms - Terms to write; validated and checked for duplicate names first.
|
|
46
|
-
*/
|
|
47
|
-
export async function writeVocabulary(root: string, terms: readonly Term[]): Promise<void> {
|
|
48
|
-
const file = vocabularyPathFor(root);
|
|
49
|
-
const validated = parseFile(VocabularySchema, file, { terms }).terms;
|
|
50
|
-
rejectDuplicates(file, validated);
|
|
51
|
-
|
|
52
|
-
const sorted = [...validated].sort(
|
|
53
|
-
(a, b) => a.term.localeCompare(b.term, "en", { sensitivity: "base" }) || a.term.localeCompare(b.term),
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
await Bun.write(file, `${[TITLE, ...sorted.map(renderTerm)].join("\n\n")}\n`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* @brief Renders one term as its `## ` block in the vocabulary file.
|
|
36
|
+
* A term runs until the next term line or heading. Its `_Avoid_:` line is its avoid list, the rest its definition.
|
|
61
37
|
*
|
|
62
|
-
* @param
|
|
38
|
+
* @param text - Full text of the glossary.
|
|
39
|
+
* @returns One raw term per bold term line, ready for `parseFile`.
|
|
63
40
|
*/
|
|
64
|
-
function
|
|
65
|
-
const lines
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*
|
|
74
|
-
* The last `_Avoid_:` line of a block is its avoid list.
|
|
75
|
-
*
|
|
76
|
-
* @param file - Path of the file, used in the error message.
|
|
77
|
-
* @param text - Full text of the vocabulary file.
|
|
78
|
-
* @returns One raw entry per heading, ready for `parseFile`.
|
|
79
|
-
* @throws FileValidationError when text sits above the first heading.
|
|
80
|
-
*/
|
|
81
|
-
function parseTerms(file: string, text: string): unknown[] {
|
|
82
|
-
const terms: { term: string; lines: string[] }[] = [];
|
|
83
|
-
|
|
84
|
-
text.split("\n").forEach((line, index) => {
|
|
85
|
-
if (line.startsWith(TERM_HEADING)) {
|
|
86
|
-
terms.push({ term: line.slice(TERM_HEADING.length).trim(), lines: [] });
|
|
87
|
-
return;
|
|
41
|
+
function parseTerms(text: string): unknown[] {
|
|
42
|
+
const terms: { term: string; lines: string[]; avoid: string[] }[] = [];
|
|
43
|
+
let open = false;
|
|
44
|
+
|
|
45
|
+
for (const line of text.split("\n")) {
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed.startsWith("#")) {
|
|
48
|
+
open = false;
|
|
49
|
+
continue;
|
|
88
50
|
}
|
|
89
51
|
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
52
|
+
const termMatch = TERM_LINE.exec(trimmed);
|
|
53
|
+
if (termMatch) {
|
|
54
|
+
terms.push({ term: (termMatch[1] ?? "").trim(), lines: [termMatch[2] ?? ""], avoid: [] });
|
|
55
|
+
open = true;
|
|
56
|
+
continue;
|
|
94
57
|
}
|
|
95
58
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{ field: `line ${index + 1}`, message: `"${line}" sits above the first ${TERM_HEADING.trim()} heading` },
|
|
99
|
-
]);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
59
|
+
const current = terms.at(-1);
|
|
60
|
+
if (!open || current === undefined) continue;
|
|
102
61
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
: (lines[avoidAt] ?? "").slice(AVOID_PREFIX.length).split(",").map((word) => word.trim()).filter((word) => word !== "");
|
|
108
|
-
const definition = lines.filter((_, index) => index !== avoidAt).join("\n").trim();
|
|
62
|
+
const avoidMatch = AVOID_LINE.exec(trimmed);
|
|
63
|
+
if (avoidMatch) current.avoid = (avoidMatch[1] ?? "").split(",").map((word) => word.trim()).filter((word) => word !== "");
|
|
64
|
+
else current.lines.push(line);
|
|
65
|
+
}
|
|
109
66
|
|
|
110
|
-
|
|
111
|
-
});
|
|
67
|
+
return terms.map(({ term, lines, avoid }) => ({ term, definition: lines.join("\n").trim(), avoid }));
|
|
112
68
|
}
|
|
113
69
|
|
|
114
70
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/src/vocabulary/index.ts
|
|
2
|
-
export {
|
|
2
|
+
export { GLOSSARY_FILE, readGlossary } from "./file";
|
|
3
3
|
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
4
|
+
export { termsMentioned } from "./match";
|
|
5
|
+
export type { TermMention } from "./match";
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
// packages/g3n-skills/skills/task-manager/task-manager/src/vocabulary/match.ts
|
|
2
2
|
import type { Term } from "../schemas/term";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Term found by `findTerm`, with how the name matched it.
|
|
6
|
-
*
|
|
7
|
-
* @property term - The matched term.
|
|
8
|
-
* @property matched - `"term"` when the name is the term itself, `"avoid"` when it is one of the term's avoided words.
|
|
9
|
-
*/
|
|
10
|
-
export interface TermMatch {
|
|
11
|
-
term: Term;
|
|
12
|
-
matched: "term" | "avoid";
|
|
13
|
-
}
|
|
14
|
-
|
|
15
4
|
/**
|
|
16
5
|
* Term a text uses, with the avoided words the text used for it.
|
|
17
6
|
*
|
|
18
7
|
* @property term - Name of the term.
|
|
19
|
-
* @property definition - Definition of the term, as the
|
|
8
|
+
* @property definition - Definition of the term, as the glossary holds it.
|
|
20
9
|
* @property avoided - Avoided words the text uses in place of `term`.
|
|
21
10
|
*/
|
|
22
11
|
export interface TermMention {
|
|
@@ -25,26 +14,6 @@ export interface TermMention {
|
|
|
25
14
|
avoided: string[];
|
|
26
15
|
}
|
|
27
16
|
|
|
28
|
-
/** Tells whether two words are equal, ignoring case. */
|
|
29
|
-
function sameWord(a: string, b: string): boolean {
|
|
30
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @brief Finds the term that answers to `name`, by its own name first and by an avoided word second.
|
|
35
|
-
*
|
|
36
|
-
* @param terms - Vocabulary to search.
|
|
37
|
-
* @param name - Word to look up, compared ignoring case.
|
|
38
|
-
* @returns The match, or `null` when no term has the name.
|
|
39
|
-
*/
|
|
40
|
-
export function findTerm(terms: readonly Term[], name: string): TermMatch | null {
|
|
41
|
-
const own = terms.find((term) => sameWord(term.term, name));
|
|
42
|
-
if (own !== undefined) return { term: own, matched: "term" };
|
|
43
|
-
|
|
44
|
-
const replaced = terms.find((term) => term.avoid.some((word) => sameWord(word, name)));
|
|
45
|
-
return replaced === undefined ? null : { term: replaced, matched: "avoid" };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
17
|
/** Tells whether `word` occurs in `text` as a whole word, ignoring case. */
|
|
49
18
|
function mentions(text: string, word: string): boolean {
|
|
50
19
|
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -54,10 +23,10 @@ function mentions(text: string, word: string): boolean {
|
|
|
54
23
|
/**
|
|
55
24
|
* @brief Finds the terms `text` uses, by name or by an avoided word, in the order of `terms`.
|
|
56
25
|
*
|
|
57
|
-
* Used by the `get-entry`
|
|
26
|
+
* Used by the `get-entry` and `next-entry` hooks, which attach each mention to its entry.
|
|
58
27
|
*
|
|
59
28
|
* @param text - Text to search, such as an entry's title, summary and body.
|
|
60
|
-
* @param terms -
|
|
29
|
+
* @param terms - Glossary to search.
|
|
61
30
|
* @returns One mention per term the text uses.
|
|
62
31
|
*/
|
|
63
32
|
export function termsMentioned(text: string, terms: readonly Term[]): TermMention[] {
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: vocabulary
|
|
3
|
-
description: Use when about to name a domain concept in code, a task or a doc, when the user corrects a word, when two words are in use for one thing, or when another skill needs a term looked up or recorded.
|
|
4
|
-
registryDependencies: [task-manager, domain-modeling]
|
|
5
|
-
disable-model-invocation: false
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Every session starts without the last one's words, so names _drift_: the _phase_ of Monday is the _stage_ of Tuesday, and by Friday the code has both. The vocabulary is the one list that stops it. Each **term** carries a definition and the **avoided words** it replaces.
|
|
9
|
-
|
|
10
|
-
Its hooks are scripts in its own `hooks/` folder, run as `bun <vocabulary-dir>/hooks/<hook>.ts`, where `<vocabulary-dir>` is this skill's base directory: the folder holding this `SKILL.md`.
|
|
11
|
-
|
|
12
|
-
The terms live in `vocabulary.md` at the tree root. **Reach them through the hooks only.** `guard-tree` denies a direct read, and a hand edit is overwritten by the next `set-term`.
|
|
13
|
-
|
|
14
|
-
| Hook | Does | Use it when |
|
|
15
|
-
|---|---|---|
|
|
16
|
-
| `get-term` | Returns one term, found by its name or by an avoided word | about to use a word for a domain concept |
|
|
17
|
-
| `list-terms` | Returns every term, sorted by name | starting work in an unfamiliar area |
|
|
18
|
-
| `set-term` | Adds a term, or replaces its definition and avoided words. **Writes.** | a concept got a name, or a name got sharper |
|
|
19
|
-
| `remove-term` | Deletes one term. **Writes.** | the concept left the project |
|
|
20
|
-
|
|
21
|
-
Copy each invocation from [`references/usage.md`](references/usage.md).
|
|
22
|
-
|
|
23
|
-
They share `--project`, `--root` and `--pretty` with the task-manager hooks. `get-term` and `remove-term` exit `1` when nothing answers to the name. `list-terms` prints `[]` for an empty vocabulary.
|
|
24
|
-
|
|
25
|
-
## Reading an answer
|
|
26
|
-
|
|
27
|
-
`get-term` reports how it found the term in `matched`:
|
|
28
|
-
|
|
29
|
-
- **`"term"`** means the word is the right one. Use it as spelled.
|
|
30
|
-
- **`"avoid"`** means the word is a retired synonym. Use the returned `term` instead, in prose and in identifiers alike.
|
|
31
|
-
|
|
32
|
-
`get-entry` and `next-entry` attach a `vocabulary` array to every plan, phase and task they return. It lists each term the entry mentions, and under `avoided` the retired words the entry still uses. Treat a non-empty `avoided` as a defect in the entry and write the term in whatever you produce from it.
|
|
33
|
-
|
|
34
|
-
## Recording a term
|
|
35
|
-
|
|
36
|
-
**Record on the second sighting.** One use of a word is prose. The same concept named twice, or named differently by two people, is a term. A correction from the user is always a term.
|
|
37
|
-
|
|
38
|
-
- **Definition.** One or two sentences saying what the thing _is_. Leave out how it is built.
|
|
39
|
-
- **`--avoid`.** The synonyms actually seen, comma-separated. `set-term` refuses a word another term already owns, which is how two terms are kept from claiming one meaning.
|
|
40
|
-
- **`set-term` replaces.** Pass the full definition and the full avoid list every time, since flags left out are cleared.
|
|
41
|
-
|
|
42
|
-
A term whose meaning is still contested is sharpened with the `/domain-modeling` skill first, then stored here.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// packages/g3n-skills/skills/task-manager/vocabulary/hooks/get-term.ts
|
|
2
|
-
import { findTerm, readVocabulary } from "@task-manager/vocabulary";
|
|
3
|
-
import { printJson, requireFlag, resolveRoot, runHook } from "@task-manager/hooks";
|
|
4
|
-
|
|
5
|
-
/** Prints the term `--term` names, found by its name or by one of its avoided words. */
|
|
6
|
-
async function run(argv: readonly string[]): Promise<void> {
|
|
7
|
-
const root = await resolveRoot(argv);
|
|
8
|
-
const name = requireFlag(argv, "--term", "phase");
|
|
9
|
-
|
|
10
|
-
const match = findTerm(await readVocabulary(root), name);
|
|
11
|
-
if (match === null) {
|
|
12
|
-
throw new Error(`"${name}" is not in the vocabulary, as a term or as an avoided word`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
printJson({ ...match.term, matched: match.matched }, argv);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
runHook(() => run(process.argv.slice(2)));
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// packages/g3n-skills/skills/task-manager/vocabulary/hooks/list-terms.ts
|
|
2
|
-
import { readVocabulary } from "@task-manager/vocabulary";
|
|
3
|
-
import { printJson, resolveRoot, runHook } from "@task-manager/hooks";
|
|
4
|
-
|
|
5
|
-
/** Prints every term in the vocabulary. */
|
|
6
|
-
async function run(argv: readonly string[]): Promise<void> {
|
|
7
|
-
const root = await resolveRoot(argv);
|
|
8
|
-
|
|
9
|
-
printJson(await readVocabulary(root), argv);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
runHook(() => run(process.argv.slice(2)));
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// packages/g3n-skills/skills/task-manager/vocabulary/hooks/remove-term.ts
|
|
2
|
-
import { findTerm, readVocabulary, vocabularyPathFor, writeVocabulary } from "@task-manager/vocabulary";
|
|
3
|
-
import { printJson, requireFlag, resolveRoot, runHook } from "@task-manager/hooks";
|
|
4
|
-
|
|
5
|
-
/** Removes the term named by `--term` from the vocabulary and prints it. */
|
|
6
|
-
async function run(argv: readonly string[]): Promise<void> {
|
|
7
|
-
const root = await resolveRoot(argv);
|
|
8
|
-
const name = requireFlag(argv, "--term", "phase");
|
|
9
|
-
|
|
10
|
-
const terms = await readVocabulary(root);
|
|
11
|
-
const match = findTerm(terms, name);
|
|
12
|
-
|
|
13
|
-
if (match === null || match.matched !== "term") {
|
|
14
|
-
throw new Error(`"${name}" is not a term in the vocabulary`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
await writeVocabulary(root, terms.filter((term) => term !== match.term));
|
|
18
|
-
|
|
19
|
-
printJson({ file: vocabularyPathFor(root), removed: match.term }, argv);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
runHook(() => run(process.argv.slice(2)));
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// packages/g3n-skills/skills/task-manager/vocabulary/hooks/set-term.ts
|
|
2
|
-
import { findTerm, readVocabulary, vocabularyPathFor, writeVocabulary } from "@task-manager/vocabulary";
|
|
3
|
-
import { printJson, readFlag, requireFlag, resolveRoot, runHook } from "@task-manager/hooks";
|
|
4
|
-
|
|
5
|
-
/** Splits a comma-separated `--avoid` value into trimmed words; empty when the flag is absent. */
|
|
6
|
-
function splitWords(list: string | undefined): string[] {
|
|
7
|
-
return list === undefined ? [] : list.split(",").map((word) => word.trim()).filter((word) => word !== "");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Adds the term `--term` or replaces its definition and avoided words, and prints the result. */
|
|
11
|
-
async function run(argv: readonly string[]): Promise<void> {
|
|
12
|
-
const root = await resolveRoot(argv);
|
|
13
|
-
|
|
14
|
-
const name = requireFlag(argv, "--term", "phase");
|
|
15
|
-
const definition = requireFlag(argv, "--definition", '"One step of a plan."');
|
|
16
|
-
const avoid = splitWords(readFlag(argv, "--avoid"));
|
|
17
|
-
|
|
18
|
-
const terms = await readVocabulary(root);
|
|
19
|
-
const existing = findTerm(terms, name);
|
|
20
|
-
|
|
21
|
-
if (existing?.matched === "avoid") {
|
|
22
|
-
throw new Error(`"${name}" is an avoided word of "${existing.term.term}"; define that term instead`);
|
|
23
|
-
}
|
|
24
|
-
for (const word of avoid) {
|
|
25
|
-
const owner = findTerm(terms, word);
|
|
26
|
-
if (owner !== null && owner.term !== existing?.term) {
|
|
27
|
-
throw new Error(`cannot avoid "${word}": it already belongs to the term "${owner.term.term}"`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const term = { term: existing?.term.term ?? name, definition, avoid };
|
|
32
|
-
await writeVocabulary(root, [...terms.filter((t) => t !== existing?.term), term]);
|
|
33
|
-
|
|
34
|
-
printJson({ file: vocabularyPathFor(root), created: existing === null, ...term }, argv);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
runHook(() => run(process.argv.slice(2)));
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Term hooks — usage
|
|
2
|
-
|
|
3
|
-
How each hook is called, and what the file they write looks like. This is the disclosed reference for the [`vocabulary`](../SKILL.md) skill.
|
|
4
|
-
|
|
5
|
-
## Invocations
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
bun <vocabulary-dir>/hooks/get-term.ts --term stage
|
|
9
|
-
bun <vocabulary-dir>/hooks/list-terms.ts --pretty
|
|
10
|
-
bun <vocabulary-dir>/hooks/set-term.ts --term Phase --definition "One step of a plan, holding tasks." --avoid "stage, milestone"
|
|
11
|
-
bun <vocabulary-dir>/hooks/remove-term.ts --term Phase
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## The file
|
|
15
|
-
|
|
16
|
-
`set-term` and `remove-term` rewrite `vocabulary.md` whole, terms sorted by name:
|
|
17
|
-
|
|
18
|
-
```md
|
|
19
|
-
# Vocabulary
|
|
20
|
-
|
|
21
|
-
## Phase
|
|
22
|
-
|
|
23
|
-
One step of a plan, holding tasks.
|
|
24
|
-
|
|
25
|
-
_Avoid_: stage, milestone
|
|
26
|
-
```
|