@g3nomaly/g3n-skills 0.1.2 → 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 +11 -8
- package/README.md +8 -3
- package/package.json +2 -2
- package/skills/agent/.claude-plugin/plugin.json +2 -2
- package/skills/agent/bootstrap/SKILL.md +1 -1
- package/skills/agent/commit/SKILL.md +17 -0
- package/skills/agent/commit/agents/openai.yaml +5 -0
- package/skills/agent/commit/hooks/check-message.ts +107 -0
- package/skills/agent/worktrees/SKILL.md +10 -12
- package/skills/agent/worktrees/agents/openai.yaml +1 -1
- package/skills/coding/.claude-plugin/plugin.json +2 -2
- package/skills/coding/analyze/SKILL.md +2 -1
- package/skills/coding/code/SKILL.md +1 -1
- package/skills/coding/document/SKILL.md +1 -1
- package/skills/coding/vocabulary/SKILL.md +47 -0
- package/skills/coding/vocabulary/agents/openai.yaml +5 -0
- package/skills/grilling/domain-modeling/SKILL.md +2 -1
- package/skills/grilling/grilling/SKILL.md +2 -1
- package/skills/speech/.claude-plugin/plugin.json +3 -2
- package/skills/speech/emphasise/SKILL.md +1 -1
- package/skills/speech/hooks.json +15 -0
- package/skills/speech/no-questions/SKILL.md +9 -0
- package/skills/speech/no-questions/agents/openai.yaml +5 -0
- package/skills/speech/no-questions/hooks/refuse-questions.ts +108 -0
- package/skills/speech/no-questions/hooks/toggle.ts +14 -0
- package/skills/speech/no-questions/src/state.ts +15 -0
- package/skills/speech/plain-language/SKILL.md +9 -0
- package/skills/speech/plain-language/agents/openai.yaml +5 -0
- package/skills/speech/unslop/SKILL.md +3 -2
- package/skills/task-manager/.claude-plugin/plugin.json +1 -2
- package/skills/task-manager/decisions/SKILL.md +1 -1
- 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/workflow/execute/SKILL.md +1 -1
- package/skills/workflow/plan/SKILL.md +1 -1
- package/skills/workflow/review/SKILL.md +1 -1
- package/skills/workflow/run/SKILL.md +1 -1
- package/skills/workflow/sync/SKILL.md +1 -1
- package/skills/workflow/worktree/SKILL.md +1 -1
- package/skills/writing/writing-great-skills/SKILL.md +3 -1
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: unslop
|
|
3
|
-
description:
|
|
3
|
+
description: Use whenever writing or editing any text.
|
|
4
|
+
disable-model-invocation: false
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# Unslop
|
|
@@ -77,4 +78,4 @@ Removing patterns is half the job. Sterile, voiceless writing is just as obvious
|
|
|
77
78
|
28. **Shorten or split dense sentences.** If the reader has to backtrack to parse a sentence, break it in two or drop clauses. One idea per sentence.
|
|
78
79
|
29. **Active voice.** Prefer it. Catch "is/are/was/were + past participle" and name the actor: "queries are validated" becomes "the compiler validates queries", "the file is parsed by the loader" becomes "the loader parses the file". Passive is fine only when the actor is unknown or genuinely doesn't matter.
|
|
79
80
|
30. **Cut adverbs, or use a stronger verb.** "runs quickly" becomes "is fast" or the number. "significantly improves" becomes the measured delta. An adverb propping up a weak verb means the verb is wrong.
|
|
80
|
-
31. **Prefer the plain word.** "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer.
|
|
81
|
+
31. **Prefer the plain word.** "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer.
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: decisions
|
|
3
|
-
description:
|
|
3
|
+
description: Use when a hard-to-reverse choice is made while planning or working a phase, when a choice replaces an earlier one, when the user asks why something was done a certain way, or when another skill needs a plan's decisions.
|
|
4
4
|
registryDependencies: [task-manager, domain-modeling]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: execute
|
|
3
|
-
description:
|
|
3
|
+
description: Use as the execute step of the workflow, when a phase has open or in-progress tasks, or when review sent a task back.
|
|
4
4
|
registryDependencies: [task-manager, worktree, vocabulary, decisions, run]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan
|
|
3
|
-
description:
|
|
3
|
+
description: Use as the plan step of the workflow, when a phase has no tasks yet, or when the user wants a phase broken down.
|
|
4
4
|
registryDependencies: [task-manager, writing-great-tasks, vocabulary, decisions, grilling, run]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: review
|
|
3
|
-
description:
|
|
3
|
+
description: Use as the review step of the workflow, when every remaining task of a phase is in-review, or when the user wants a phase's work checked before it is merged.
|
|
4
4
|
registryDependencies: [task-manager, writing-great-tasks, vocabulary, decisions, run]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: run
|
|
3
|
-
description:
|
|
3
|
+
description: Use when the user wants a phase worked or names a phase to run, or when the orchestrator skill hands over a phase.
|
|
4
4
|
registryDependencies: [task-manager, plan, execute, review, sync, orchestrator]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sync
|
|
3
|
-
description:
|
|
3
|
+
description: Use as the sync step of the workflow, or when every task of a phase is completed and the phase is still open.
|
|
4
4
|
registryDependencies: [task-manager, worktree, vocabulary, decisions, run, orchestrator]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: worktree
|
|
3
|
-
description:
|
|
3
|
+
description: Use when a task is about to be started, when a finished phase is being merged, when worktrees are left over, or when another skill needs a worktree.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: writing-great-skills
|
|
3
3
|
description: Use when writing or editing a skill, its SKILL.md, references or hooks.
|
|
4
|
-
registryDependencies: [emphasise, writing-great-hooks]
|
|
4
|
+
registryDependencies: [emphasise, plain-language, writing-great-hooks]
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -9,12 +9,14 @@ disable-model-invocation: false
|
|
|
9
9
|
|
|
10
10
|
- `name`: the skill's folder name.
|
|
11
11
|
- `description`: decides whether the agent loads the skill. Say when to use it: the tasks and words that should trigger it, and nothing else.
|
|
12
|
+
- `disable-model-invocation`: always set. `false`: the agent may load the skill, so `description` says only when to use it. `true`: only the user runs it, so `description` may describe it.
|
|
12
13
|
|
|
13
14
|
## SKILL.md
|
|
14
15
|
|
|
15
16
|
- Direct instructions, in order. Each step says when it is done.
|
|
16
17
|
- Only what every use of the skill needs.
|
|
17
18
|
- Cut every line the agent would follow without being told.
|
|
19
|
+
- Write in plain language with the `/plain-language` skill.
|
|
18
20
|
- Mark bold, italics and backticks with the `/emphasise` skill.
|
|
19
21
|
|
|
20
22
|
## references/
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: vocabulary
|
|
3
|
-
description: Keep one word per concept across sessions, stored in the task-manager tree and reached through hooks. 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)));
|