skillwiki 0.2.1-beta.15 → 0.2.1-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -73,7 +73,8 @@ var ExitCode = {
73
73
  RAW_DEDUP_DETECTED: 33,
74
74
  MIGRATION_APPLIED: 34,
75
75
  UNKNOWN_WIKI_PROFILE: 35,
76
- DEDUP_APPLIED: 36
76
+ DEDUP_APPLIED: 36,
77
+ PROJECT_NOT_FOUND: 37
77
78
  };
78
79
 
79
80
  // ../shared/src/json-output.ts
@@ -120,12 +121,10 @@ var RawSourceSchema = z.object({
120
121
  sha256: sha256Hex,
121
122
  project: wikilink.optional(),
122
123
  work_item: wikilink.optional(),
123
- kind: z.enum(["postmortem", "session-log", "meeting-notes", "other"]).optional()
124
+ kind: z.enum(["postmortem", "session-log", "meeting-notes", "other", "idea", "bug", "task", "note"]).optional()
124
125
  }).superRefine((v, ctx) => {
125
- const projectFields = [v.project, v.work_item, v.kind];
126
- const present = projectFields.filter((x) => x !== void 0).length;
127
- if (present !== 0 && present !== 3) {
128
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "project, work_item, kind must all be set together" });
126
+ if (v.work_item !== void 0 && (v.project === void 0 || v.kind === void 0)) {
127
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "project and kind are required when work_item is set" });
129
128
  }
130
129
  });
131
130
  var WorkItemSchema = z.object({
@@ -1341,11 +1340,11 @@ async function runInit(input) {
1341
1340
  const errTemplate = await writeOrPreserve(`${TEMPLATE_FOLDER}/tpl-ad-hoc-capture.md`, async () => {
1342
1341
  return [
1343
1342
  "---",
1344
- "project: ",
1345
- "tags: []",
1346
- "priority: ",
1347
- "created: {{date:YYYY-MM-DD}}T{{time:HH:mm}}",
1348
- "ingested: ",
1343
+ "source_url:",
1344
+ "ingested: {{date:YYYY-MM-DD}}",
1345
+ "sha256: # run: skillwiki hash <this-file>",
1346
+ "kind: # idea | bug | task | note | other",
1347
+ 'project: # optional: "[[slug]]"',
1349
1348
  "---",
1350
1349
  "",
1351
1350
  ""
@@ -2782,6 +2781,129 @@ async function runTranscripts(input) {
2782
2781
  return { exitCode: ExitCode.OK, result: ok({ transcripts, humanHint: hint }) };
2783
2782
  }
2784
2783
 
2784
+ // src/commands/project-index.ts
2785
+ import { readdir as readdir5, readFile as readFile16, writeFile as writeFile11, mkdir as mkdir6 } from "fs/promises";
2786
+ import { join as join20, dirname as dirname8 } from "path";
2787
+ var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
2788
+ async function runProjectIndex(input) {
2789
+ const slug = input.slug;
2790
+ const projectDir = join20(input.vault, "projects", slug);
2791
+ try {
2792
+ await readdir5(projectDir);
2793
+ } catch {
2794
+ return {
2795
+ exitCode: ExitCode.PROJECT_NOT_FOUND,
2796
+ result: err("PROJECT_NOT_FOUND", { slug, path: projectDir })
2797
+ };
2798
+ }
2799
+ const wikilinkPattern = `[[${slug}]]`;
2800
+ const entries = [];
2801
+ for (const dir of LAYER2_DIRS) {
2802
+ let files;
2803
+ try {
2804
+ files = await readdir5(join20(input.vault, dir), { withFileTypes: true });
2805
+ } catch {
2806
+ continue;
2807
+ }
2808
+ for (const entry of files) {
2809
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2810
+ const filePath = join20(input.vault, dir, entry.name);
2811
+ let text;
2812
+ try {
2813
+ text = await readFile16(filePath, "utf8");
2814
+ } catch {
2815
+ continue;
2816
+ }
2817
+ const fm = extractFrontmatter(text);
2818
+ if (!fm.ok) continue;
2819
+ const pp = fm.data.provenance_projects;
2820
+ if (!Array.isArray(pp) || !pp.some((p) => String(p) === wikilinkPattern)) continue;
2821
+ entries.push({
2822
+ page: `${dir}/${entry.name}`,
2823
+ type: fm.data.type ?? dir.slice(0, -1),
2824
+ // singularize dir name
2825
+ title: fm.data.title ?? entry.name.replace(/\.md$/, "")
2826
+ });
2827
+ }
2828
+ }
2829
+ const typeOrder = { entity: 0, concept: 1, comparison: 2, query: 3, summary: 4, meta: 5 };
2830
+ entries.sort((a, b) => {
2831
+ const ta = typeOrder[a.type] ?? 99;
2832
+ const tb = typeOrder[b.type] ?? 99;
2833
+ return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
2834
+ });
2835
+ const indexPath = join20(projectDir, "knowledge.md");
2836
+ let existing = false;
2837
+ let stale = false;
2838
+ try {
2839
+ const existingText = await readFile16(indexPath, "utf8");
2840
+ existing = true;
2841
+ const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
2842
+ const existingPages = new Set(existingEntries.map((l) => {
2843
+ const m = l.match(/\[\[([^\]]+)\]\]/);
2844
+ return m ? m[1] : "";
2845
+ }));
2846
+ const currentPages = new Set(entries.map((e) => e.page.replace(/\.md$/, "")));
2847
+ stale = existingPages.size !== currentPages.size || [...currentPages].some((p) => !existingPages.has(p));
2848
+ } catch {
2849
+ }
2850
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2851
+ const grouped = /* @__PURE__ */ new Map();
2852
+ for (const e of entries) {
2853
+ const group = e.type;
2854
+ if (!grouped.has(group)) grouped.set(group, []);
2855
+ grouped.get(group).push(e);
2856
+ }
2857
+ let body = `# Knowledge Index: ${slug}
2858
+
2859
+ Autogenerated by \`skillwiki project-index\` on ${today}.
2860
+
2861
+ `;
2862
+ for (const [type, items] of grouped) {
2863
+ body += `## ${type}
2864
+
2865
+ `;
2866
+ for (const item of items) {
2867
+ const pageRef = item.page.replace(/\.md$/, "");
2868
+ body += `- [[${pageRef}]] \u2014 ${item.title}
2869
+ `;
2870
+ }
2871
+ body += "\n";
2872
+ }
2873
+ if (entries.length === 0) {
2874
+ body += `No Layer 2 pages reference \`[[${slug}]]\` in provenance_projects.
2875
+ `;
2876
+ }
2877
+ if (input.apply) {
2878
+ try {
2879
+ await mkdir6(dirname8(indexPath), { recursive: true });
2880
+ await writeFile11(indexPath, body, "utf8");
2881
+ } catch (e) {
2882
+ return {
2883
+ exitCode: ExitCode.WRITE_FAILED,
2884
+ result: err("WRITE_FAILED", { file: indexPath, message: String(e) })
2885
+ };
2886
+ }
2887
+ }
2888
+ const action = input.apply ? `written ${entries.length} entries to ${indexPath}` : `${entries.length} entries found (use --apply to write)`;
2889
+ const staleHint = stale ? " (STALE \u2014 existing index outdated)" : existing ? " (up to date)" : "";
2890
+ return {
2891
+ exitCode: ExitCode.OK,
2892
+ result: ok({
2893
+ slug,
2894
+ entries,
2895
+ existing,
2896
+ stale,
2897
+ index_path: `projects/${slug}/knowledge.md`,
2898
+ humanHint: `project: ${slug}
2899
+ entries: ${entries.length}${staleHint}
2900
+ ${action}
2901
+
2902
+ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e.title}`).join("\n")}`
2903
+ })
2904
+ };
2905
+ }
2906
+
2785
2907
  // src/cli.ts
2786
2908
  var pkg = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8"));
2787
2909
  var program = new Command();
@@ -2947,6 +3069,11 @@ program.command("transcripts [vault]").description("list transcript files in raw
2947
3069
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2948
3070
  else emit(await runTranscripts({ vault: v.vault, since: opts.since }));
2949
3071
  });
3072
+ program.command("project-index <slug> [vault]").description("generate a knowledge index for a project workspace").option("--apply", "write knowledge.md to the project directory", false).option("--wiki <name>", "wiki profile name").action(async (slug, vault, opts) => {
3073
+ const v = await resolveVaultArg(vault, opts.wiki);
3074
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
3075
+ else emit(await runProjectIndex({ vault: v.vault, slug, apply: !!opts.apply }));
3076
+ });
2950
3077
  triggerAutoUpdate(process.env.HOME ?? "", pkg.version);
2951
3078
  program.parseAsync(process.argv).catch((e) => {
2952
3079
  process.stdout.write(JSON.stringify({ ok: false, error: "INTERNAL", detail: { message: String(e) } }) + "\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.1-beta.15",
3
+ "version": "0.2.1-beta.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.1-beta.15",
3
+ "version": "0.2.1-beta.16",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 15 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.2.1-beta.15",
3
+ "version": "0.2.1-beta.16",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -9,8 +9,8 @@ Capture ad-hoc ideas, bugs, tasks, and notes into the vault. Three entry points
9
9
 
10
10
  | Entry | When | What happens |
11
11
  |-------|------|-------------|
12
- | `/wiki-add-task <text>` | You're in a Claude session | Appends entry to `raw/transcripts/YYYY-MM-DD-ad-hoc-captures.md` |
13
- | Filesystem drop | You're NOT in a Claude session (Obsidian, editor, sync) | Create/edit any file in `raw/transcripts/` — dev-loop discovers it on next cycle |
12
+ | `/wiki-add-task <text>` | You're in a Claude session | Creates `raw/transcripts/YYYY-MM-DD-{type}-{slug}.md` with raw-valid frontmatter |
13
+ | Filesystem drop | You're NOT in a Claude session (Obsidian, editor, sync) | Create any `.md` file in `raw/transcripts/` using the vault template — dev-loop discovers it on next cycle |
14
14
  | Dev-loop discovery | Automatic, next cycle | Scans `raw/transcripts/` for new files since last cycle, surfaces as claimable work |
15
15
 
16
16
  ## When This Skill Activates
@@ -30,52 +30,76 @@ Run `skillwiki lang` at the start. Entry prose and `--human` summaries use the r
30
30
  - `text` — the idea/bug/task/note content (required)
31
31
  - `type` — one of: `idea`, `bug`, `task`, `note` (default: `idea`)
32
32
  - `project` — optional project slug to cross-reference (e.g., `llm-wiki`)
33
- 2. **Determine target file.** The capture file is `raw/transcripts/YYYY-MM-DD-ad-hoc-captures.md` where YYYY-MM-DD is today's date. If the file exists, append; otherwise create it with standard raw frontmatter.
34
- 3. **Write the entry.** Append to the capture file:
33
+ 2. **Build filename.** Derive a short slug from the text (lowercase, hyphenated, max 8 words). The capture file is `raw/transcripts/YYYY-MM-DD-{type}-{slug}.md`. Each capture gets its own file never append to an existing file.
34
+ 3. **Write frontmatter.** Create the file with raw-source frontmatter:
35
+ ```yaml
36
+ ---
37
+ source_url:
38
+ ingested: YYYY-MM-DD
39
+ ingested_by: manual
40
+ sha256:
41
+ kind: {type}
42
+ project: "[[{slug}]]"
43
+ ---
44
+ ```
45
+ - Set `kind` to the parsed type (`idea`, `bug`, `task`, `note`).
46
+ - If a `project` slug was provided, set `project: "[[slug]]"`.
47
+ - If no project, omit the `project` field entirely.
48
+ - Leave `sha256` empty for now — step 5 fills it in.
49
+ - `source_url` is null (these are locally originated captures).
50
+ 4. **Write body.** Below the frontmatter, write:
35
51
  ```markdown
36
- ### HH:MM — [type]
52
+ # {type}: {text}
37
53
 
38
- [text]
39
-
40
- <!---meta: {"captured_at": "YYYY-MM-DDTHH:MM:SS", "type": "[type]"}--->
54
+ {text}
41
55
  ```
42
- - Use 24-hour time for HH:MM.
43
- - Do not overwrite or modify existing entries.
44
- 4. **Cross-reference (optional).** If a `project` slug was provided:
56
+ Use the resolved output language for any prose. The type label and frontmatter stay English.
57
+ 5. **Compute and write sha256.** Run `skillwiki hash <file>` to get the SHA-256 of the body. Update the `sha256:` field in the frontmatter with the computed value. This makes the file validate as a raw source.
58
+ 6. **Cross-reference (optional).** If a `project` slug was provided:
45
59
  - Check that `projects/{slug}/` exists in the vault.
46
- - Append a one-line reference to the project's work log or compound notes:
47
- `- [YYYY-MM-DD] capture: [text] → raw/transcripts/YYYY-MM-DD-ad-hoc-captures.md`
60
+ - Append a one-line reference to the project's compound notes:
61
+ `- [YYYY-MM-DD] capture: [text (first 60 chars)] → raw/transcripts/YYYY-MM-DD-{type}-{slug}.md`
48
62
  - Do NOT create a full work item (that's `proj-work`'s job).
49
- 5. **Update log.md.** Append: `## [YYYY-MM-DD] capture | [type]: [text (first 60 chars)]`
50
- 6. **Confirm to user.** Report what was captured and where. Suggest next steps:
63
+ 7. **Update log.md.** Append: `## [YYYY-MM-DD] capture | [type]: [text (first 60 chars)]`
64
+ 8. **Confirm to user.** Report what was captured and where. Suggest next steps:
51
65
  - If `type: idea` → "Consider ingesting related sources to develop this idea."
52
66
  - If `type: bug` → "Use proj-work to create a bug-fix work item."
53
67
  - If `type: task` → "Use proj-work to track this task through the dev loop."
54
68
  - If `type: note` → "Will be available for future wiki-query searches."
55
69
 
56
- ## Ad-hoc captures file format
70
+ ## Capture file format
57
71
 
58
- The file `raw/transcripts/YYYY-MM-DD-ad-hoc-captures.md` is a standard raw source with frontmatter:
72
+ Each capture is a standalone raw source file with valid frontmatter:
59
73
 
60
74
  ```yaml
61
75
  ---
62
76
  source_url:
63
- ingested: YYYY-MM-DD
64
- sha256:
77
+ ingested: 2026-05-08
78
+ ingested_by: manual
79
+ sha256: <64-char hex computed over body>
80
+ kind: idea
81
+ project: "[[llm-wiki]]"
65
82
  ---
83
+
84
+ # idea: Fix the template mismatch
85
+
86
+ Fix the template mismatch between wiki-add-task and the vault template.
66
87
  ```
67
88
 
68
- The `sha256` is computed over the body after the closing `---`. On each append, recompute and update `sha256`. This keeps source-drift detection functional even though the file grows throughout the day.
89
+ The `kind` field uses the capture type and must be one of: `idea`, `bug`, `task`, `note` (plus the existing `postmortem`, `session-log`, `meeting-notes`, `other` for non-capture raw sources).
90
+
91
+ The `project` and `kind` fields can be set independently — they do not require `work_item`. The `work_item` field is only used when the raw source is directly tied to a project work item (set by `proj-work`).
69
92
 
70
93
  ## Stop conditions
71
94
 
72
95
  - `skillwiki path` returns NO_VAULT_CONFIGURED.
73
96
  - No `text` provided (prompt user once, then stop).
97
+ - Target file already exists (use a different slug or add a suffix).
74
98
 
75
99
  ## Forbidden
76
100
 
77
101
  - Creating an `inbox/` directory. All captures go to `raw/transcripts/`.
78
- - Modifying existing entries in the captures file only append.
102
+ - Appending to existing capture files each capture gets its own file.
79
103
  - Creating a work item — this is capture-only. Use `proj-work` for full work items.
80
104
  - Writing to any Layer 2 or Layer 3 location. Captures are Layer 1 (raw).
81
105
 
@@ -83,16 +107,10 @@ The `sha256` is computed over the body after the closing `---`. On each append,
83
107
 
84
108
  When you're not in a Claude session, drop files directly into `raw/transcripts/`:
85
109
 
86
- 1. Create any `.md` file in `raw/transcripts/` — name it descriptively (e.g., `2026-05-07-idea-xyz.md`)
87
- 2. Add raw frontmatter at the top:
88
- ```yaml
89
- ---
90
- source_url:
91
- ingested: YYYY-MM-DD
92
- sha256:
93
- ---
94
- ```
110
+ 1. Create a `.md` file in `raw/transcripts/` — name it descriptively (e.g., `2026-05-08-idea-fix-template.md`)
111
+ 2. Use the vault template at `_Templates/tpl-ad-hoc-capture.md` for frontmatter scaffolding
95
112
  3. Write your idea/bug/task/note below the frontmatter
113
+ 4. Run `skillwiki hash <file>` when you're back in a session to fill in sha256
96
114
 
97
115
  No special format required — the dev-loop QUERY step will discover new files on the next cycle and surface them as claimable work. Mark the type with a heading like `## idea`, `## bug`, `## task`, or just write freeform.
98
116
 
@@ -12,3 +12,7 @@
12
12
  - `architecture/` — how it's designed (incl. ADRs).
13
13
  - `work/YYYY-MM-DD-{slug}/` — per-work-item folders containing `spec.md`, `plan.md`, `log.md`.
14
14
  - `compound/` — project-local concrete learnings.
15
+
16
+ ## Knowledge
17
+
18
+ Run `skillwiki project-index {{slug}} --apply` to generate `knowledge.md` listing all Layer 2 pages that reference `[[{{slug}}]]` in their provenance_projects.