@haaaiawd/loom 0.8.0 → 0.10.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/cli/bin/loom.js +190 -87
- package/cli/src/auto.js +41 -18
- package/cli/src/diagnostics.js +270 -191
- package/cli/src/guide.js +92 -25
- package/cli/src/philosophy.js +51 -17
- package/cli/src/verify.js +38 -8
- package/package.json +4 -4
package/cli/src/guide.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { readCurrentPointer } from './version.js';
|
|
8
8
|
import { loadIntentMap } from './intent-map.js';
|
|
9
|
-
import { isAutoOn, writeHeartbeat, needsHumanReview } from './auto.js';
|
|
9
|
+
import { isAutoOn, getAutoMode, writeHeartbeat, needsHumanReview } from './auto.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* 检测文件是否还是模板(未填充真实内容)。
|
|
@@ -36,17 +36,78 @@ function isTemplate(filePath) {
|
|
|
36
36
|
/**
|
|
37
37
|
* 诊断项目当前阶段。
|
|
38
38
|
* @param {string} projectDir — 项目根目录
|
|
39
|
-
* @returns {{ stage: string, stage_num: number, details: object, auto: boolean, next_action: string, next_command: string, message: string, needs_human_review: boolean }}
|
|
40
|
-
*/
|
|
41
|
-
export function guideProject(projectDir, options = {}) {
|
|
42
|
-
const cwd = projectDir || process.cwd();
|
|
43
|
-
const loomRoot = join(cwd, '.loom');
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
* @returns {{ stage: string, stage_num: number, details: object, auto: boolean, next_action: string, next_command: string, message: string, needs_human_review: boolean }}
|
|
40
|
+
*/
|
|
41
|
+
export function guideProject(projectDir, options = {}) {
|
|
42
|
+
const cwd = projectDir || process.cwd();
|
|
43
|
+
const loomRoot = join(cwd, '.loom');
|
|
44
|
+
const autoMode = getAutoMode(loomRoot);
|
|
45
|
+
const auto = autoMode !== 'manual'; // 向后兼容 boolean
|
|
46
|
+
const result = diagnoseStage(cwd, loomRoot, auto);
|
|
47
|
+
result.auto_mode = autoMode;
|
|
48
|
+
|
|
49
|
+
// 按 stage 补充可执行信息:要读什么、要产出什么、完成后跑什么校验
|
|
50
|
+
const current = result.details.version || 'v1';
|
|
51
|
+
const stageMeta = {
|
|
52
|
+
not_initialized: {
|
|
53
|
+
inputs: [],
|
|
54
|
+
outputs: ['.loom/v1/'],
|
|
55
|
+
verify_command: 'loom guide',
|
|
56
|
+
},
|
|
57
|
+
no_version: {
|
|
58
|
+
inputs: [],
|
|
59
|
+
outputs: ['.loom/v1/'],
|
|
60
|
+
verify_command: 'loom guide',
|
|
61
|
+
},
|
|
62
|
+
need_philosophy: {
|
|
63
|
+
inputs: ['meta/PHILOSOPHY_WEAVER.md', 'meta/BASELINE.md', 'dimensions/PART_DECOMPOSITION.md', 'dimensions/SEARCH_METHODOLOGY.md'],
|
|
64
|
+
outputs: [`.loom/${current}/00_PHILOSOPHY/PRODUCT_PHILOSOPHY.md`, `.loom/${current}/00_PHILOSOPHY/ENGINEERING_CREED.md`, `.loom/${current}/00_PHILOSOPHY/DECISION_RUBRIC.md`],
|
|
65
|
+
verify_command: 'loom philosophy check',
|
|
66
|
+
},
|
|
67
|
+
need_vision: {
|
|
68
|
+
inputs: ['roles/visionary.md', `.loom/${current}/00_PHILOSOPHY/`],
|
|
69
|
+
outputs: [`.loom/${current}/01_VISION.md`],
|
|
70
|
+
verify_command: 'loom guide',
|
|
71
|
+
},
|
|
72
|
+
need_architecture: {
|
|
73
|
+
inputs: ['roles/architect.md', `.loom/${current}/01_VISION.md`],
|
|
74
|
+
outputs: [`.loom/${current}/02_ARCHITECTURE.md`, `.loom/${current}/04_INTENT_MAP.json`],
|
|
75
|
+
verify_command: 'loom doctor',
|
|
76
|
+
},
|
|
77
|
+
intent_map_broken: {
|
|
78
|
+
inputs: [`.loom/${current}/04_INTENT_MAP.json`],
|
|
79
|
+
outputs: [`.loom/${current}/04_INTENT_MAP.json`],
|
|
80
|
+
verify_command: 'loom intent validate',
|
|
81
|
+
},
|
|
82
|
+
in_loop: {
|
|
83
|
+
inputs: ['roles/forge.md', 'roles/keeper.md', `.loom/${current}/04_INTENT_MAP.json`],
|
|
84
|
+
outputs: ['代码文件', `.loom/${current}/verifications/INT-*.json`],
|
|
85
|
+
verify_command: 'loom verify pending',
|
|
86
|
+
},
|
|
87
|
+
ready_for_loop: {
|
|
88
|
+
inputs: ['roles/forge.md', 'roles/keeper.md', `.loom/${current}/04_INTENT_MAP.json`],
|
|
89
|
+
outputs: ['代码文件', `.loom/${current}/verifications/INT-*.json`],
|
|
90
|
+
verify_command: 'loom verify pending',
|
|
91
|
+
},
|
|
92
|
+
all_done: {
|
|
93
|
+
inputs: [],
|
|
94
|
+
outputs: [],
|
|
95
|
+
verify_command: 'loom doctor',
|
|
96
|
+
},
|
|
97
|
+
unknown: {
|
|
98
|
+
inputs: [],
|
|
99
|
+
outputs: [],
|
|
100
|
+
verify_command: 'loom doctor',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const meta = stageMeta[result.stage] || {};
|
|
104
|
+
result.inputs = meta.inputs || [];
|
|
105
|
+
result.outputs = meta.outputs || [];
|
|
106
|
+
result.verify_command = meta.verify_command || null;
|
|
107
|
+
// 统一后处理:写心跳 + 加 AUTO 提示词 + 判断是否需要人类 review
|
|
108
|
+
if (existsSync(loomRoot) && !options.dryRun) {
|
|
109
|
+
try {
|
|
110
|
+
writeHeartbeat(loomRoot, {
|
|
50
111
|
stage: result.stage,
|
|
51
112
|
stage_num: result.stage_num,
|
|
52
113
|
next_command: result.next_command,
|
|
@@ -57,13 +118,19 @@ export function guideProject(projectDir, options = {}) {
|
|
|
57
118
|
result.needs_human_review = needsHumanReview(loomRoot, result.stage_num);
|
|
58
119
|
// 在 message 末尾加 AUTO 提示
|
|
59
120
|
if (result.stage_num > 0 && result.stage_num < 4) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
121
|
+
if (autoMode === 'auto-design') {
|
|
122
|
+
result.message += '\n\n> AUTO 模式 auto-design——设计阶段也自动执行,无需人类确认。';
|
|
123
|
+
} else if (autoMode === 'auto-loop') {
|
|
124
|
+
result.message += '\n\n> ⚠ 这是设计阶段(哲学/愿景/架构),auto-loop 模式下需要人类 review 后再继续。';
|
|
125
|
+
} else {
|
|
126
|
+
result.message += '\n\n> ⚠ AUTO 模式 manual——每步都需要人类确认后再继续。';
|
|
127
|
+
}
|
|
128
|
+
} else if (result.stage_num >= 4) {
|
|
129
|
+
if (auto) {
|
|
130
|
+
result.message += '\n\n> AUTO 模式开启——直接执行 next_command,无需人类确认。';
|
|
131
|
+
} else {
|
|
132
|
+
result.message += '\n\n> ⚠ AUTO 模式关闭——执行 next_command 后等人类确认再继续。';
|
|
133
|
+
}
|
|
67
134
|
}
|
|
68
135
|
return result;
|
|
69
136
|
}
|
|
@@ -145,11 +212,11 @@ function diagnoseStage(cwd, loomRoot, auto) {
|
|
|
145
212
|
}
|
|
146
213
|
|
|
147
214
|
// 状态 4-7: Intent Map 已设计,根据 Intent 状态判断
|
|
148
|
-
let intentMap;
|
|
149
|
-
let intents;
|
|
150
|
-
try {
|
|
151
|
-
intentMap = loadIntentMap(versionDir);
|
|
152
|
-
intents = intentMap.intents;
|
|
215
|
+
let intentMap;
|
|
216
|
+
let intents;
|
|
217
|
+
try {
|
|
218
|
+
intentMap = loadIntentMap(versionDir);
|
|
219
|
+
intents = intentMap.intents;
|
|
153
220
|
} catch (e) {
|
|
154
221
|
return {
|
|
155
222
|
stage: 'intent_map_broken',
|
|
@@ -220,7 +287,7 @@ function diagnoseStage(cwd, loomRoot, auto) {
|
|
|
220
287
|
if (counts.needs_review > 0) {
|
|
221
288
|
const reviewIds = allIntents.filter((i) => i.status === 'needs_review').map((i) => i.id);
|
|
222
289
|
// 读 _meta.pass_count 收敛趟计数(最大 3 趟)
|
|
223
|
-
const passCount = intentMap._meta?.pass_count || 1;
|
|
290
|
+
const passCount = intentMap._meta?.pass_count || 1;
|
|
224
291
|
const MAX_PASSES = 3;
|
|
225
292
|
const isOverLimit = passCount > MAX_PASSES;
|
|
226
293
|
const passMsg = ` [Pass ${passCount}/${MAX_PASSES}]`;
|
package/cli/src/philosophy.js
CHANGED
|
@@ -62,10 +62,13 @@ const REASON_KEYWORDS = ['萃取', '理由', '为什么', '因为', '启发', '
|
|
|
62
62
|
* 从哲学文档内容中提取"灵感来源"章节的条目。
|
|
63
63
|
* @param {string} content — MD 全文
|
|
64
64
|
* @returns {Array<{ raw: string, name: string, urls: string[], hasReason: boolean }>}
|
|
65
|
+
* 如果返回空数组,调用方需区分"没有章节"和"有章节但没条目"——
|
|
66
|
+
* 用 hasInspirationSection() 单独判断。
|
|
65
67
|
*/
|
|
66
68
|
function parseInspirationSources(content) {
|
|
67
|
-
// 匹配 "## 灵感来源" 或 "## Inspiration"
|
|
68
|
-
|
|
69
|
+
// 匹配 "## 灵感来源" 或 "## Inspiration Sources" 等章节
|
|
70
|
+
// 支持 {#anchor} 后缀和多种标题变体
|
|
71
|
+
const sectionMatch = content.match(/^##\s+(?:灵感来源|Inspiration|参考来源|References?|参考文献|参考资料|Sources|Bibliography)/m);
|
|
69
72
|
if (!sectionMatch) return [];
|
|
70
73
|
|
|
71
74
|
const startIdx = sectionMatch.index + sectionMatch[0].length;
|
|
@@ -75,24 +78,29 @@ function parseInspirationSources(content) {
|
|
|
75
78
|
? content.slice(startIdx, startIdx + nextSection.index)
|
|
76
79
|
: content.slice(startIdx);
|
|
77
80
|
|
|
78
|
-
// 解析每个 list item(-
|
|
81
|
+
// 解析每个 list item(- / * / 1. / 2. 等开头的无序或有序列表)
|
|
79
82
|
const items = [];
|
|
80
83
|
const lines = sectionText.split('\n');
|
|
81
84
|
let currentItem = null;
|
|
82
85
|
|
|
86
|
+
// 匹配 - xxx / * xxx / 1. xxx / 2. xxx 等
|
|
87
|
+
const ITEM_RE = /^\s*(?:[-*]|\d+\.)\s+/;
|
|
88
|
+
// URL 匹配:https:// / file:// / local:./path / local:/abs/path
|
|
89
|
+
const URL_RE = /(?:https?:|file:)[\/]+[^\s))]+|local:[^\s))]+/g;
|
|
90
|
+
|
|
83
91
|
for (const line of lines) {
|
|
84
|
-
if (
|
|
92
|
+
if (ITEM_RE.test(line)) {
|
|
85
93
|
// 新条目
|
|
86
94
|
if (currentItem) items.push(currentItem);
|
|
87
|
-
const raw = line.replace(
|
|
88
|
-
const urls = [...raw.matchAll(
|
|
95
|
+
const raw = line.replace(ITEM_RE, '').trim();
|
|
96
|
+
const urls = [...raw.matchAll(URL_RE)].map((m) => m[0]);
|
|
89
97
|
const name = raw.replace(/\*\*/g, '').split(/[((——]/)[0].trim();
|
|
90
98
|
const hasReason = REASON_KEYWORDS.some((kw) => raw.includes(kw));
|
|
91
99
|
currentItem = { raw, name, urls, hasReason };
|
|
92
100
|
} else if (currentItem && line.trim()) {
|
|
93
101
|
// 多行条目的续行
|
|
94
102
|
currentItem.raw += ' ' + line.trim();
|
|
95
|
-
const newUrls = [...line.matchAll(
|
|
103
|
+
const newUrls = [...line.matchAll(URL_RE)].map((m) => m[0]);
|
|
96
104
|
currentItem.urls.push(...newUrls);
|
|
97
105
|
if (REASON_KEYWORDS.some((kw) => line.includes(kw))) {
|
|
98
106
|
currentItem.hasReason = true;
|
|
@@ -103,6 +111,14 @@ function parseInspirationSources(content) {
|
|
|
103
111
|
return items;
|
|
104
112
|
}
|
|
105
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 检查哲学文档是否有"灵感来源"章节(不管有没有条目)。
|
|
116
|
+
* 用来区分"没有章节"和"有章节但没条目"两种情况。
|
|
117
|
+
*/
|
|
118
|
+
function hasInspirationSection(content) {
|
|
119
|
+
return /^##\s+(?:灵感来源|Inspiration|参考来源|References?|参考文献|参考资料|Sources|Bibliography)/m.test(content);
|
|
120
|
+
}
|
|
121
|
+
|
|
106
122
|
/**
|
|
107
123
|
* 判断 URL 是否为 Wikipedia 链接。
|
|
108
124
|
*/
|
|
@@ -173,12 +189,26 @@ export function validateInspirationSources(philosophyDir) {
|
|
|
173
189
|
}
|
|
174
190
|
}
|
|
175
191
|
|
|
176
|
-
//
|
|
192
|
+
// 如果没有任何文件提取到灵感来源条目
|
|
177
193
|
if (allSources.length === 0) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
// 区分两种情况:完全没有章节 vs 有章节但没条目
|
|
195
|
+
const filesWithSection = [];
|
|
196
|
+
for (const file of files) {
|
|
197
|
+
const content = readFileSync(join(philosophyDir, file), 'utf-8');
|
|
198
|
+
if (hasInspirationSection(content)) filesWithSection.push(file);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (filesWithSection.length > 0) {
|
|
202
|
+
issues.push({
|
|
203
|
+
severity: 'high',
|
|
204
|
+
msg: `${filesWithSection.join(', ')} 有"灵感来源"章节但没有可识别的条目。章节里可能是模板占位符。需要 Weaver 真正走搜索漏斗,填入至少 ${MIN_SOURCES} 个源(- **源名** — 理由。来源:URL 格式)。`,
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
issues.push({
|
|
208
|
+
severity: 'high',
|
|
209
|
+
msg: '所有哲学文档都没有"灵感来源"章节。PHILOSOPHY_WEAVER.md 要求哲学文档必须包含灵感来源(参考了哪些机构、人物、流派——附 URL 和理由)。支持的标题:灵感来源 / Inspiration / 参考来源 / References / 参考文献 / 参考资料 / Sources / Bibliography。',
|
|
210
|
+
});
|
|
211
|
+
}
|
|
182
212
|
}
|
|
183
213
|
|
|
184
214
|
return {
|
|
@@ -204,7 +234,7 @@ export function validatePartDecomposition(philosophyDir) {
|
|
|
204
234
|
|
|
205
235
|
const files = listPhilosophyFiles(philosophyDir);
|
|
206
236
|
|
|
207
|
-
// 搜索"实现部分"
|
|
237
|
+
// 搜索"实现部分"相关章节——支持 {#anchor} 后缀和多种标题变体
|
|
208
238
|
const PART_SECTION_PATTERNS = [
|
|
209
239
|
/^##\s+实现部分清单/m,
|
|
210
240
|
/^##\s+部分拆解/m,
|
|
@@ -212,11 +242,15 @@ export function validatePartDecomposition(philosophyDir) {
|
|
|
212
242
|
/^##\s+Part Decomposition/m,
|
|
213
243
|
/^##\s+Implementation Parts/m,
|
|
214
244
|
/^##\s+拆解出的部分/m,
|
|
245
|
+
/^##\s+Implementation Decomposition/m,
|
|
246
|
+
/^##\s+Parts? /m,
|
|
215
247
|
];
|
|
216
248
|
|
|
217
|
-
//
|
|
249
|
+
// 搜索部分条目——支持无序列表、有序列表、树形符号
|
|
218
250
|
const PART_ITEM_PATTERNS = [
|
|
219
|
-
/^\s*[-*]\s+\*\*(.+?)\*\*/gm,
|
|
251
|
+
/^\s*[-*]\s+\*\*(.+?)\*\*/gm, // - **CLI 交互设计**
|
|
252
|
+
/^\s*\d+\.\s+\*\*(.+?)\*\*/gm, // 1. **CLI 交互设计**
|
|
253
|
+
/^\s*\d+\.\s+(.+)/gm, // 1. CLI 交互设计
|
|
220
254
|
/^\s*├──\s+(.+)/gm, // ├── CLI 交互设计
|
|
221
255
|
/^\s*└──\s+(.+)/gm, // └── 产物设计
|
|
222
256
|
];
|
|
@@ -257,12 +291,12 @@ export function validatePartDecomposition(philosophyDir) {
|
|
|
257
291
|
if (!foundSection) {
|
|
258
292
|
issues.push({
|
|
259
293
|
severity: 'high',
|
|
260
|
-
msg: '哲学文档没有"实现部分清单"章节。PHILOSOPHY_WEAVER.md Step 2 要求按 PART_DECOMPOSITION.md
|
|
294
|
+
msg: '哲学文档没有"实现部分清单"章节。PHILOSOPHY_WEAVER.md Step 2 要求按 PART_DECOMPOSITION.md 拆解实现部分,并在哲学文档中显式列出。支持的标题:实现部分清单 / 部分拆解 / 实现部分 / Part Decomposition / Implementation Parts / 拆解出的部分。',
|
|
261
295
|
});
|
|
262
296
|
} else if (parts.length < 2) {
|
|
263
297
|
issues.push({
|
|
264
298
|
severity: 'medium',
|
|
265
|
-
msg:
|
|
299
|
+
msg: `找到"实现部分清单"章节但仅识别到 ${parts.length} 个部分。可能章节里是模板占位符,或条目格式不被识别(用 - **部分名** 或 ├── 部分名 格式)。PART_DECOMPOSITION.md 建议小项目 3-5 个部分,大项目 6-10 个。`,
|
|
266
300
|
});
|
|
267
301
|
}
|
|
268
302
|
|
package/cli/src/verify.js
CHANGED
|
@@ -92,14 +92,14 @@ export function writeVerification(verificationsDir, record) {
|
|
|
92
92
|
data = { intent_id: record.intent_id, records: [] };
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// 计算轮次和连续 deviated 计数。规范要求中间出现 passed/blocked 后重置。
|
|
96
|
-
const round = data.records.length + 1;
|
|
97
|
-
const recordsWithCurrent = [...data.records, record];
|
|
98
|
-
let deviatedCount = 0;
|
|
99
|
-
for (let i = recordsWithCurrent.length - 1; i >= 0; i--) {
|
|
100
|
-
if (recordsWithCurrent[i].verdict !== 'deviated') break;
|
|
101
|
-
deviatedCount++;
|
|
102
|
-
}
|
|
95
|
+
// 计算轮次和连续 deviated 计数。规范要求中间出现 passed/blocked 后重置。
|
|
96
|
+
const round = data.records.length + 1;
|
|
97
|
+
const recordsWithCurrent = [...data.records, record];
|
|
98
|
+
let deviatedCount = 0;
|
|
99
|
+
for (let i = recordsWithCurrent.length - 1; i >= 0; i--) {
|
|
100
|
+
if (recordsWithCurrent[i].verdict !== 'deviated') break;
|
|
101
|
+
deviatedCount++;
|
|
102
|
+
}
|
|
103
103
|
|
|
104
104
|
// 追加新记录
|
|
105
105
|
data.records.push({
|
|
@@ -134,6 +134,36 @@ export function getVerificationHistory(verificationsDir, intentId) {
|
|
|
134
134
|
return readJsonFile(filePath, '验证记录');
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 快捷创建验证记录——Agent 不用手动构造完整 JSON。
|
|
139
|
+
* 内部用 summary 填充四个维度的 evidence,生成标准记录格式。
|
|
140
|
+
* @param {string} verificationsDir — verifications/ 目录路径
|
|
141
|
+
* @param {string} intentId — 如 "INT-001"
|
|
142
|
+
* @param {string} verdict — 'passed' | 'deviated' | 'blocked'
|
|
143
|
+
* @param {string} summary — 验证摘要(也会作为四个维度的 evidence)
|
|
144
|
+
* @param {object} [extras]
|
|
145
|
+
* @param {string} [extras.reproduction_command] — 复现命令
|
|
146
|
+
* @param {string} [extras.deviation_detail] — 偏离说明(deviated 时)
|
|
147
|
+
* @returns {{ filePath: string, round: number, deviated_count: number, should_escalate: boolean }}
|
|
148
|
+
*/
|
|
149
|
+
export function createQuickVerification(verificationsDir, intentId, verdict, summary, extras = {}) {
|
|
150
|
+
const timestamp = new Date().toISOString();
|
|
151
|
+
// 用 summary 填充四个维度的 evidence——快捷命令不要求 Agent 逐维度写
|
|
152
|
+
const dimensions = {};
|
|
153
|
+
for (const dim of REQUIRED_DIMENSIONS) {
|
|
154
|
+
dimensions[dim] = { verdict, evidence: summary };
|
|
155
|
+
}
|
|
156
|
+
return writeVerification(verificationsDir, {
|
|
157
|
+
intent_id: intentId,
|
|
158
|
+
verdict,
|
|
159
|
+
timestamp,
|
|
160
|
+
summary,
|
|
161
|
+
dimensions,
|
|
162
|
+
reproduction_command: extras.reproduction_command || null,
|
|
163
|
+
deviation_detail: extras.deviation_detail || null,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
/**
|
|
138
168
|
* 返回所有待验证的 Intent(有实现产物但还没验证记录的)。
|
|
139
169
|
* 需要传入 Intent Map 来判断哪些 Intent 是 in_progress。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/loom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "LOOM — 哲学驱动开发框架。Agent 通过 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/Haaaiawd/loom.git"
|
|
39
|
-
},
|
|
40
|
-
"homepage": "https://github.com/Haaaiawd/loom#readme",
|
|
38
|
+
"url": "git+https://github.com/Haaaiawd/loom.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/Haaaiawd/loom#readme",
|
|
41
41
|
"author": "haaaiawd"
|
|
42
42
|
}
|