@warpgogol/forge 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/dist/bin/cli.js +1 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +77 -5
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/notes/index.d.ts +2 -0
- package/dist/os/notes/index.d.ts.map +1 -0
- package/dist/os/notes/index.js +10 -0
- package/dist/os/notes/index.js.map +1 -0
- package/dist/os/notes/notes.module.d.ts +3 -0
- package/dist/os/notes/notes.module.d.ts.map +1 -0
- package/dist/os/notes/notes.module.js +83 -0
- package/dist/os/notes/notes.module.js.map +1 -0
- package/dist/os/rfc/handlers/validate.d.ts.map +1 -1
- package/dist/os/rfc/handlers/validate.js +5 -5
- package/dist/os/rfc/handlers/validate.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/onboarding/invariant-engine.d.ts.map +1 -1
- package/dist/src/onboarding/invariant-engine.js +137 -5
- package/dist/src/onboarding/invariant-engine.js.map +1 -1
- package/dist/src/profiles/profile-schema.d.ts +16 -1
- package/dist/src/profiles/profile-schema.d.ts.map +1 -1
- package/dist/src/profiles/profile-schema.js +16 -1
- package/dist/src/profiles/profile-schema.js.map +1 -1
- package/dist/src/profiles/stack-profile.d.ts +5 -0
- package/dist/src/profiles/stack-profile.d.ts.map +1 -1
- package/dist/src/profiles/stack-profile.js +3 -0
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/dist/src/validators/note-frontmatter-validate.d.ts +20 -0
- package/dist/src/validators/note-frontmatter-validate.d.ts.map +1 -0
- package/dist/src/validators/note-frontmatter-validate.js +117 -0
- package/dist/src/validators/note-frontmatter-validate.js.map +1 -0
- package/dist/src/validators/note-link-validate.d.ts +21 -0
- package/dist/src/validators/note-link-validate.d.ts.map +1 -0
- package/dist/src/validators/note-link-validate.js +121 -0
- package/dist/src/validators/note-link-validate.js.map +1 -0
- package/dist/src/validators/note-orphan-detect.d.ts +19 -0
- package/dist/src/validators/note-orphan-detect.d.ts.map +1 -0
- package/dist/src/validators/note-orphan-detect.js +123 -0
- package/dist/src/validators/note-orphan-detect.js.map +1 -0
- package/package.json +17 -1
- package/profiles/obsidian-vault.yaml +103 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Validate wikilink integrity across an Obsidian vault — scans [[wikilinks]] and resolves each against the note graph.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
6
|
+
<item>Do not implement domain-specific logic beyond wikilink resolution.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0808: initial note.link.validate command handler.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { parse as parseYaml } from "yaml";
|
|
16
|
+
function collectMarkdownFiles(dir, baseDir, results) {
|
|
17
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", ".turbo", "dist", ".cache", ".obsidian"]);
|
|
18
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name))
|
|
21
|
+
continue;
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
const relPath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
collectMarkdownFiles(fullPath, baseDir, results);
|
|
26
|
+
}
|
|
27
|
+
else if (entry.name.endsWith(".md")) {
|
|
28
|
+
results.push(relPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function runNoteLinkValidate(input, context) {
|
|
33
|
+
const workspaceRoot = context.workspaceRoot ?? process.cwd();
|
|
34
|
+
const vaultDir = input.flags["vault-dir"] ?? "vault";
|
|
35
|
+
const pathFilter = input.flags["path"];
|
|
36
|
+
const scanDir = pathFilter
|
|
37
|
+
? path.join(workspaceRoot, vaultDir, pathFilter)
|
|
38
|
+
: path.join(workspaceRoot, vaultDir);
|
|
39
|
+
if (!fs.existsSync(scanDir)) {
|
|
40
|
+
return {
|
|
41
|
+
data: {
|
|
42
|
+
command: "note.link.validate",
|
|
43
|
+
violations: [],
|
|
44
|
+
count: 0,
|
|
45
|
+
},
|
|
46
|
+
exitCode: 0,
|
|
47
|
+
summary: "note.link.validate: vault directory not found — 0 violations",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const allMdFiles = [];
|
|
51
|
+
collectMarkdownFiles(scanDir, workspaceRoot, allMdFiles);
|
|
52
|
+
const noteMap = new Map();
|
|
53
|
+
for (const mdFile of allMdFiles) {
|
|
54
|
+
const basename = path.basename(mdFile, ".md");
|
|
55
|
+
noteMap.set(basename, mdFile);
|
|
56
|
+
const fullPath = path.join(workspaceRoot, mdFile);
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
59
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
60
|
+
if (fmMatch) {
|
|
61
|
+
try {
|
|
62
|
+
const fm = parseYaml(fmMatch[1]);
|
|
63
|
+
if (fm.aliases && Array.isArray(fm.aliases)) {
|
|
64
|
+
for (const alias of fm.aliases) {
|
|
65
|
+
if (typeof alias === "string")
|
|
66
|
+
noteMap.set(alias, mdFile);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// skip malformed frontmatter
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// skip unreadable files
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const linkRegex = /\[\[([^\]]+?)\]\]/g;
|
|
80
|
+
const violations = [];
|
|
81
|
+
for (const file of allMdFiles) {
|
|
82
|
+
const fullPath = path.join(workspaceRoot, file);
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
85
|
+
const lines = content.split(/\r?\n/);
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
let match;
|
|
88
|
+
linkRegex.lastIndex = 0;
|
|
89
|
+
while ((match = linkRegex.exec(lines[i])) !== null) {
|
|
90
|
+
const linkTarget = match[1].split("|")[0].split("#")[0].trim();
|
|
91
|
+
if (!linkTarget)
|
|
92
|
+
continue;
|
|
93
|
+
if (!noteMap.has(linkTarget)) {
|
|
94
|
+
violations.push({
|
|
95
|
+
file,
|
|
96
|
+
line: i + 1,
|
|
97
|
+
link: match[0],
|
|
98
|
+
rule: "NOTE-01",
|
|
99
|
+
message: `Wikilink target '${linkTarget}' not found in vault`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// skip unreadable files
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
data: {
|
|
111
|
+
command: "note.link.validate",
|
|
112
|
+
violations,
|
|
113
|
+
count: violations.length,
|
|
114
|
+
},
|
|
115
|
+
exitCode: violations.length > 0 ? 1 : 0,
|
|
116
|
+
summary: violations.length > 0
|
|
117
|
+
? `note.link.validate: ${violations.length} violation(s) found`
|
|
118
|
+
: "note.link.validate: OK — 0 violations",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=note-link-validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-link-validate.js","sourceRoot":"","sources":["../../../src/validators/note-link-validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ForgeCommandResult } from "../types.ts";
|
|
2
|
+
interface NoteOrphanDetectInput {
|
|
3
|
+
flags: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface OrphanReport {
|
|
6
|
+
file: string;
|
|
7
|
+
inboundLinks: 0;
|
|
8
|
+
severity: "warning";
|
|
9
|
+
}
|
|
10
|
+
export interface NoteOrphanDetectResult {
|
|
11
|
+
command: "note.orphan.detect";
|
|
12
|
+
orphans: OrphanReport[];
|
|
13
|
+
count: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function runNoteOrphanDetect(input: NoteOrphanDetectInput, context: {
|
|
16
|
+
workspaceRoot?: string;
|
|
17
|
+
}): ForgeCommandResult<NoteOrphanDetectResult>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=note-orphan-detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-orphan-detect.d.ts","sourceRoot":"","sources":["../../../src/validators/note-orphan-detect.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,CAAC,CAAC;IAChB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAiBD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,kBAAkB,CAAC,sBAAsB,CAAC,CA6F5C"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Detect orphan notes in an Obsidian vault — notes with zero inbound wikilinks.</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not import from @warpgogol/* — this module is portable.</item>
|
|
6
|
+
<item>Do not implement domain-specific logic beyond orphan detection.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0808: initial note.orphan.detect command handler.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { parse as parseYaml } from "yaml";
|
|
16
|
+
function collectMarkdownFiles(dir, baseDir, results) {
|
|
17
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", ".turbo", "dist", ".cache", ".obsidian"]);
|
|
18
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
if (entry.isDirectory() && SKIP_DIRS.has(entry.name))
|
|
21
|
+
continue;
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
const relPath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
collectMarkdownFiles(fullPath, baseDir, results);
|
|
26
|
+
}
|
|
27
|
+
else if (entry.name.endsWith(".md")) {
|
|
28
|
+
results.push(relPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function runNoteOrphanDetect(input, context) {
|
|
33
|
+
const workspaceRoot = context.workspaceRoot ?? process.cwd();
|
|
34
|
+
const vaultDir = input.flags["vault-dir"] ?? "vault";
|
|
35
|
+
const scanDir = path.join(workspaceRoot, vaultDir);
|
|
36
|
+
if (!fs.existsSync(scanDir)) {
|
|
37
|
+
return {
|
|
38
|
+
data: {
|
|
39
|
+
command: "note.orphan.detect",
|
|
40
|
+
orphans: [],
|
|
41
|
+
count: 0,
|
|
42
|
+
},
|
|
43
|
+
exitCode: 0,
|
|
44
|
+
summary: "note.orphan.detect: vault directory not found — 0 orphans",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const allMdFiles = [];
|
|
48
|
+
collectMarkdownFiles(scanDir, workspaceRoot, allMdFiles);
|
|
49
|
+
const noteMap = new Map();
|
|
50
|
+
for (const mdFile of allMdFiles) {
|
|
51
|
+
const basename = path.basename(mdFile, ".md");
|
|
52
|
+
noteMap.set(basename, mdFile);
|
|
53
|
+
const fullPath = path.join(workspaceRoot, mdFile);
|
|
54
|
+
try {
|
|
55
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
56
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
57
|
+
if (fmMatch) {
|
|
58
|
+
try {
|
|
59
|
+
const fm = parseYaml(fmMatch[1]);
|
|
60
|
+
if (fm.aliases && Array.isArray(fm.aliases)) {
|
|
61
|
+
for (const alias of fm.aliases) {
|
|
62
|
+
if (typeof alias === "string")
|
|
63
|
+
noteMap.set(alias, mdFile);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// skip malformed frontmatter
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// skip unreadable files
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const inboundLinks = new Map();
|
|
77
|
+
for (const mdFile of allMdFiles) {
|
|
78
|
+
inboundLinks.set(mdFile, 0);
|
|
79
|
+
}
|
|
80
|
+
const linkRegex = /\[\[([^\]]+?)\]\]/g;
|
|
81
|
+
for (const file of allMdFiles) {
|
|
82
|
+
const fullPath = path.join(workspaceRoot, file);
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
85
|
+
let match;
|
|
86
|
+
linkRegex.lastIndex = 0;
|
|
87
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
88
|
+
const linkTarget = match[1].split("|")[0].split("#")[0].trim();
|
|
89
|
+
if (!linkTarget)
|
|
90
|
+
continue;
|
|
91
|
+
const targetFile = noteMap.get(linkTarget);
|
|
92
|
+
if (targetFile && targetFile !== file) {
|
|
93
|
+
inboundLinks.set(targetFile, (inboundLinks.get(targetFile) ?? 0) + 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// skip unreadable files
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const orphans = [];
|
|
102
|
+
for (const [file, count] of inboundLinks) {
|
|
103
|
+
if (count === 0) {
|
|
104
|
+
orphans.push({
|
|
105
|
+
file,
|
|
106
|
+
inboundLinks: 0,
|
|
107
|
+
severity: "warning",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
data: {
|
|
113
|
+
command: "note.orphan.detect",
|
|
114
|
+
orphans,
|
|
115
|
+
count: orphans.length,
|
|
116
|
+
},
|
|
117
|
+
exitCode: 0,
|
|
118
|
+
summary: orphans.length > 0
|
|
119
|
+
? `note.orphan.detect: ${orphans.length} orphan(s) found`
|
|
120
|
+
: "note.orphan.detect: OK — 0 orphans",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=note-orphan-detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-orphan-detect.js","sourceRoot":"","sources":["../../../src/validators/note-orphan-detect.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warpgogol/forge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -124,6 +124,14 @@
|
|
|
124
124
|
"./os/exploration-module": {
|
|
125
125
|
"types": "./os/exploration/exploration.module.ts",
|
|
126
126
|
"default": "./os/exploration/exploration.module.ts"
|
|
127
|
+
},
|
|
128
|
+
"./os/notes": {
|
|
129
|
+
"types": "./os/notes/index.ts",
|
|
130
|
+
"default": "./os/notes/index.ts"
|
|
131
|
+
},
|
|
132
|
+
"./os/notes-module": {
|
|
133
|
+
"types": "./os/notes/notes.module.ts",
|
|
134
|
+
"default": "./os/notes/notes.module.ts"
|
|
127
135
|
}
|
|
128
136
|
},
|
|
129
137
|
"files": [
|
|
@@ -268,6 +276,14 @@
|
|
|
268
276
|
"./os/exploration-module": {
|
|
269
277
|
"types": "./dist/os/exploration/exploration.module.d.ts",
|
|
270
278
|
"default": "./dist/os/exploration/exploration.module.js"
|
|
279
|
+
},
|
|
280
|
+
"./os/notes": {
|
|
281
|
+
"types": "./dist/os/notes/index.d.ts",
|
|
282
|
+
"default": "./dist/os/notes/index.js"
|
|
283
|
+
},
|
|
284
|
+
"./os/notes-module": {
|
|
285
|
+
"types": "./dist/os/notes/notes.module.d.ts",
|
|
286
|
+
"default": "./dist/os/notes/notes.module.js"
|
|
271
287
|
}
|
|
272
288
|
}
|
|
273
289
|
},
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
schema: forge/stack-profile@1
|
|
2
|
+
id: obsidian-vault
|
|
3
|
+
displayName: Obsidian Knowledge Base
|
|
4
|
+
detect:
|
|
5
|
+
anyOf:
|
|
6
|
+
- .obsidian/app.json
|
|
7
|
+
- vault/**/*.md
|
|
8
|
+
domain: knowledge-base
|
|
9
|
+
register: creative
|
|
10
|
+
terminology:
|
|
11
|
+
artifact: note
|
|
12
|
+
artifactPlural: notes
|
|
13
|
+
module: folder
|
|
14
|
+
source: markdown file
|
|
15
|
+
output: export
|
|
16
|
+
verify: link-check
|
|
17
|
+
operator: author
|
|
18
|
+
scriptDir: scripts
|
|
19
|
+
artifacts:
|
|
20
|
+
- id: note
|
|
21
|
+
extensions:
|
|
22
|
+
- .md
|
|
23
|
+
validate:
|
|
24
|
+
command: note.link.validate
|
|
25
|
+
- id: canvas
|
|
26
|
+
extensions:
|
|
27
|
+
- .canvas
|
|
28
|
+
- id: attachment
|
|
29
|
+
extensions:
|
|
30
|
+
- .png
|
|
31
|
+
- .jpg
|
|
32
|
+
- .jpeg
|
|
33
|
+
- .webp
|
|
34
|
+
- .svg
|
|
35
|
+
- .pdf
|
|
36
|
+
- .mp4
|
|
37
|
+
- .webm
|
|
38
|
+
workspaceTypes:
|
|
39
|
+
- id: vault
|
|
40
|
+
detect:
|
|
41
|
+
glob: "**/*.md"
|
|
42
|
+
contains: "---"
|
|
43
|
+
invariants:
|
|
44
|
+
- id: NOTE-01
|
|
45
|
+
rule: All [[wikilinks]] must resolve to an existing note file
|
|
46
|
+
severity: error
|
|
47
|
+
check:
|
|
48
|
+
kind: link-resolution
|
|
49
|
+
glob: "vault/**/*.md"
|
|
50
|
+
- id: NOTE-02
|
|
51
|
+
rule: Every note must have a title in frontmatter (title or first H1)
|
|
52
|
+
severity: warning
|
|
53
|
+
check:
|
|
54
|
+
kind: frontmatter-required
|
|
55
|
+
glob: "vault/**/*.md"
|
|
56
|
+
fields: [title]
|
|
57
|
+
- id: NOTE-03
|
|
58
|
+
rule: No executable code files in vault/ (scripts go to scripts/ per ADR-0043)
|
|
59
|
+
severity: error
|
|
60
|
+
check:
|
|
61
|
+
kind: path-exclusion
|
|
62
|
+
glob: "vault/**/*.{ts,mjs,js,py,sh}"
|
|
63
|
+
- id: NOTE-04
|
|
64
|
+
rule: Note filenames must use kebab-case (lowercase letters, digits, hyphens)
|
|
65
|
+
severity: warning
|
|
66
|
+
check:
|
|
67
|
+
kind: filename-pattern
|
|
68
|
+
glob: "vault/**/*.md"
|
|
69
|
+
pattern: "^[a-z0-9]+(-[a-z0-9]+)*\\.md$"
|
|
70
|
+
workspace:
|
|
71
|
+
dirs:
|
|
72
|
+
- vault
|
|
73
|
+
- scripts
|
|
74
|
+
- .forge
|
|
75
|
+
files:
|
|
76
|
+
- path: .gitignore
|
|
77
|
+
content: |
|
|
78
|
+
node_modules
|
|
79
|
+
.obsidian/workspace
|
|
80
|
+
.obsidian/workspace.json
|
|
81
|
+
.cache
|
|
82
|
+
operator-profile.md
|
|
83
|
+
- path: forge.yaml
|
|
84
|
+
content: |
|
|
85
|
+
schema: forge/config@1
|
|
86
|
+
project:
|
|
87
|
+
name: __PROJECT_NAME__
|
|
88
|
+
stack:
|
|
89
|
+
- obsidian-vault
|
|
90
|
+
packageManager: pnpm
|
|
91
|
+
paths:
|
|
92
|
+
rfcsDir: docs/rfcs
|
|
93
|
+
adrsDir: docs/adrs
|
|
94
|
+
- path: package.json
|
|
95
|
+
content: |
|
|
96
|
+
{
|
|
97
|
+
"name": "__PROJECT_NAME__",
|
|
98
|
+
"version": "0.1.0",
|
|
99
|
+
"private": true,
|
|
100
|
+
"type": "module"
|
|
101
|
+
}
|
|
102
|
+
install:
|
|
103
|
+
- pnpm add -D @warpgogol/forge
|