@namewta/speculo 0.8.10 → 0.8.13
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/README.md +2 -1
- package/package.json +1 -1
- package/template/canonical/canonical-specdev-goal-plan.md +27 -11
- package/template/canonical/canonical-specdev-grill-with-docs.md +1 -1
- package/template/canonical/canonical-specdev-orchestrate-implementation.md +56 -18
- package/template/canonical/canonical-specdev-spec.md +1 -1
- package/template/canonical/canonical-specdev-tickets.md +43 -6
- package/template/skills/engineering-standards-builder/README.md +6 -28
- package/template/skills/engineering-standards-builder/SKILL.md +88 -163
- package/template/skills/engineering-standards-builder/examples/README.md +2 -0
- package/template/skills/engineering-standards-builder/manifest.txt +4 -0
- package/template/skills/engineering-standards-builder/references/rules/00-governance-and-precedence.md +22 -29
- package/template/skills/engineering-standards-builder/references/rules/01-project-discovery.md +26 -59
- package/template/skills/engineering-standards-builder/references/rules/02-evidence-topology-and-scope.md +28 -55
- package/template/skills/engineering-standards-builder/references/rules/03-interview-and-decisions.md +4 -3
- package/template/skills/engineering-standards-builder/references/rules/14-generation-contract.md +64 -80
- package/template/skills/engineering-standards-builder/references/rules/15-validation-contract.md +24 -47
- package/template/skills/engineering-standards-builder/references/rules/16-language-adapter-contract.md +11 -48
- package/template/skills/engineering-standards-builder/references/rules/README.md +3 -3
- package/template/skills/engineering-standards-builder/scripts/self-test.mjs +51 -5
- package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +16 -4
- package/template/skills/engineering-standards-builder/scripts/validate-generated-skill.mjs +166 -64
- package/template/skills/engineering-standards-builder/templates/README.md +12 -3
- package/template/skills/engineering-standards-builder/templates/domain-skill/SKILL.md.template +32 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/SKILL.md.template +16 -11
- package/template/skills/engineering-standards-builder/templates/project-skill/generated-skill-set.json.template +7 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/00-project-profile.md.template +3 -1
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/01-module-map.md.template +4 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/02-decisions-and-exceptions.md.template +1 -1
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/03-skill-map.md.template +19 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/04-source-and-template-map.md.template +22 -0
- package/template/skills/engineering-standards-builder/templates/project-skill/references/project/review-checklist.md.template +2 -0
- package/template/skills/git-history-squash/SKILL.md +100 -0
- package/template/skills/git-history-squash/assets/request-template.json +18 -0
- package/template/skills/git-history-squash/references/recovery-contract.md +50 -0
- package/template/skills/git-history-squash/references/rewrite-contract.md +123 -0
- package/template/skills/git-history-squash/references/submodule-contract.md +54 -0
- package/template/skills/git-history-squash/scripts/git-history-squash.mjs +1171 -0
- package/template/workflows/specdev/I-implement/I-implement.md +18 -7
- package/template/workflows/specdev/I-implement/evidence-template.md +13 -0
- package/template/workflows/specdev/I-implement/execution-preflight.md +4 -0
- package/template/workflows/specdev/O-orchestrate-implementation/execution-loop.md +3 -2
- package/template/workflows/specdev/P-goal-plan/completion-control.md +3 -0
- package/template/workflows/specdev/P-goal-plan/lead-orchestration.md +6 -2
- package/template/workflows/specdev/README.md +1 -1
- package/template/workflows/specdev/T-tickets/T-tickets.md +16 -3
- package/template/workflows/specdev/T-tickets/ticket-readiness.md +3 -0
- package/template/workflows/specdev/T-tickets/ticket-template.md +3 -0
- package/template/workflows/specdev/T-tickets/tickets-map-template.md +15 -0
- package/template/workflows/specdev/common/rules/artifact-contract.md +1 -1
- package/template/workflows/specdev/common/skills/dev-worktree/references/finalize.md +5 -2
- package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +4 -2
- package/template/workflows/specdev/common/skills/subagent-delivery/references/external-web-subagent.md +2 -1
- package/template/workflows/specdev/common/skills/subagent-delivery/references/native-subagent.md +2 -1
- package/template/workflows/specdev/common/skills/subagent-delivery/references/source-package.md +4 -2
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +143 -3
|
@@ -324,6 +324,12 @@ function stripPathTag(value) {
|
|
|
324
324
|
return match ? match[1].trim() : null;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
function stripInlineCode(value) {
|
|
328
|
+
const trimmed = String(value).trim();
|
|
329
|
+
const match = /^`([^`]+)`$/.exec(trimmed);
|
|
330
|
+
return match ? match[1].trim() : trimmed;
|
|
331
|
+
}
|
|
332
|
+
|
|
327
333
|
function normalizeProjectPath(value) {
|
|
328
334
|
const inner = stripPathTag(value) ?? String(value);
|
|
329
335
|
return inner
|
|
@@ -1466,7 +1472,120 @@ function validateSpec(path, errors, warnings) {
|
|
|
1466
1472
|
return { path, meta, body };
|
|
1467
1473
|
}
|
|
1468
1474
|
|
|
1469
|
-
function
|
|
1475
|
+
function validateProjectSkillMatrix(body, label, errors, repoRoot = null) {
|
|
1476
|
+
const section = sectionBody(body, "### 项目 Skill 读取矩阵");
|
|
1477
|
+
if (!section) return [];
|
|
1478
|
+
|
|
1479
|
+
const lines = section.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1480
|
+
const headerIndex = lines.findIndex((line) =>
|
|
1481
|
+
line.startsWith("|") &&
|
|
1482
|
+
line.includes("Applies To") &&
|
|
1483
|
+
line.includes("Project Skill") &&
|
|
1484
|
+
line.includes("Trigger / Scope") &&
|
|
1485
|
+
line.includes("Read Timing") &&
|
|
1486
|
+
line.includes("Purpose"),
|
|
1487
|
+
);
|
|
1488
|
+
if (headerIndex < 0) {
|
|
1489
|
+
errors.push(`${label}: project Skill matrix is missing the required five-column header`);
|
|
1490
|
+
return [];
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
const entries = [];
|
|
1494
|
+
const seenSkillPaths = new Set();
|
|
1495
|
+
for (const line of lines.slice(headerIndex + 1)) {
|
|
1496
|
+
if (!line.startsWith("|")) continue;
|
|
1497
|
+
if (/^\|(?:\s*:?-{3,}:?\s*\|)+$/.test(line)) continue;
|
|
1498
|
+
const cells = line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
|
|
1499
|
+
if (cells.length !== 5) {
|
|
1500
|
+
errors.push(`${label}: project Skill matrix row must contain five columns: ${line}`);
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const appliesTo = stripInlineCode(cells[0]).split(/[\s,]+/).filter(Boolean);
|
|
1505
|
+
if (!appliesTo.length || appliesTo.some((value) => value !== "ALL" && !/^T-\d{2,}$/.test(value))) {
|
|
1506
|
+
errors.push(`${label}: invalid project Skill Applies To value '${cells[0]}'`);
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (new Set(appliesTo).size !== appliesTo.length) {
|
|
1510
|
+
errors.push(`${label}: duplicate project Skill Applies To value '${cells[0]}'`);
|
|
1511
|
+
}
|
|
1512
|
+
for (const [column, value] of [
|
|
1513
|
+
["Trigger / Scope", cells[2]],
|
|
1514
|
+
["Read Timing", cells[3]],
|
|
1515
|
+
["Purpose", cells[4]],
|
|
1516
|
+
]) {
|
|
1517
|
+
if (!value || value === "...") errors.push(`${label}: project Skill ${column} must be explicit`);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const skillValue = stripInlineCode(cells[1]);
|
|
1521
|
+
if (skillValue.startsWith("无")) {
|
|
1522
|
+
if (!`${skillValue} ${cells[2]}`.includes("已扫描")) {
|
|
1523
|
+
errors.push(`${label}: no-project-Skill row must record the scanned scope`);
|
|
1524
|
+
}
|
|
1525
|
+
entries.push({ appliesTo: new Set(appliesTo), path: null });
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
const skillPath = stripPathTag(skillValue);
|
|
1530
|
+
if (!skillPath) {
|
|
1531
|
+
errors.push(`${label}: project Skill must be a Path-tagged SKILL.md or an explicit no-Skill row`);
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
const pathIssue = validatePathValue(skillPath);
|
|
1535
|
+
if (pathIssue) errors.push(`${label}: invalid project Skill Path ${skillPath}: ${pathIssue}`);
|
|
1536
|
+
if (skillPath.includes("{roots.")) {
|
|
1537
|
+
errors.push(`${label}: project Skill Path must be project-relative, not a Speculo root Path: ${skillPath}`);
|
|
1538
|
+
}
|
|
1539
|
+
if (!skillPath.endsWith("/SKILL.md")) {
|
|
1540
|
+
errors.push(`${label}: project Skill Path must end with /SKILL.md: ${skillPath}`);
|
|
1541
|
+
}
|
|
1542
|
+
if (PLACEHOLDER_RE.test(skillPath)) {
|
|
1543
|
+
errors.push(`${label}: project Skill Path contains an unresolved placeholder: ${skillPath}`);
|
|
1544
|
+
}
|
|
1545
|
+
if (seenSkillPaths.has(skillPath)) {
|
|
1546
|
+
errors.push(`${label}: duplicate project Skill Path ${skillPath}; combine its Applies To values`);
|
|
1547
|
+
}
|
|
1548
|
+
seenSkillPaths.add(skillPath);
|
|
1549
|
+
|
|
1550
|
+
if (repoRoot && !pathIssue && !skillPath.includes("{roots.") && !PLACEHOLDER_RE.test(skillPath)) {
|
|
1551
|
+
const resolvedRepo = resolve(repoRoot);
|
|
1552
|
+
const resolvedSkill = resolve(resolvedRepo, normalizeProjectPath(skillPath));
|
|
1553
|
+
const relativeSkill = relative(resolvedRepo, resolvedSkill);
|
|
1554
|
+
if (relativeSkill === "" || relativeSkill.split(sep)[0] === "..") {
|
|
1555
|
+
errors.push(`${label}: project Skill Path escapes the project root: ${skillPath}`);
|
|
1556
|
+
} else if (!isFile(resolvedSkill)) {
|
|
1557
|
+
errors.push(`${label}: project Skill does not exist under --repo: ${skillPath}`);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
entries.push({ appliesTo: new Set(appliesTo), path: skillPath });
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if (!entries.length) errors.push(`${label}: project Skill matrix contains no data rows`);
|
|
1564
|
+
const allNoSkill = entries.some((entry) => entry.path === null && entry.appliesTo.has("ALL"));
|
|
1565
|
+
if (allNoSkill && entries.some((entry) => entry.path !== null)) {
|
|
1566
|
+
errors.push(`${label}: an ALL no-project-Skill row conflicts with listed project Skills`);
|
|
1567
|
+
}
|
|
1568
|
+
return entries;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function validateProjectSkillCoverage(matrix, ticketIds, label, errors) {
|
|
1572
|
+
if (!matrix.length) return;
|
|
1573
|
+
const knownTicketIds = new Set(ticketIds);
|
|
1574
|
+
for (const entry of matrix) {
|
|
1575
|
+
for (const appliesTo of entry.appliesTo) {
|
|
1576
|
+
if (appliesTo !== "ALL" && !knownTicketIds.has(appliesTo)) {
|
|
1577
|
+
errors.push(`${label}: project Skill matrix references missing Ticket ${appliesTo}`);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
for (const ticketId of knownTicketIds) {
|
|
1582
|
+
if (!matrix.some((entry) => entry.appliesTo.has("ALL") || entry.appliesTo.has(ticketId))) {
|
|
1583
|
+
errors.push(`${label}: project Skill matrix does not cover ${ticketId}`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function validateMap(path, errors, repoRoot = null) {
|
|
1470
1589
|
if (!isFile(path)) {
|
|
1471
1590
|
errors.push("missing Tickets Map");
|
|
1472
1591
|
return null;
|
|
@@ -1476,6 +1595,8 @@ function validateMap(path, errors) {
|
|
|
1476
1595
|
errors.push(`${basename(path)}: artifact/schema_version must be tickets-map/${DOMAIN_SCHEMA_VERSION}`);
|
|
1477
1596
|
}
|
|
1478
1597
|
for (const heading of [
|
|
1598
|
+
"### 总体实施背景",
|
|
1599
|
+
"### 项目 Skill 读取矩阵",
|
|
1479
1600
|
"## 2. 执行清单",
|
|
1480
1601
|
"## 3. 依赖 DAG",
|
|
1481
1602
|
"## 4. 合同覆盖矩阵",
|
|
@@ -1483,7 +1604,8 @@ function validateMap(path, errors) {
|
|
|
1483
1604
|
]) {
|
|
1484
1605
|
if (!body.includes(heading)) errors.push(`${basename(path)}: missing '${heading}'`);
|
|
1485
1606
|
}
|
|
1486
|
-
|
|
1607
|
+
const projectSkillMatrix = validateProjectSkillMatrix(body, basename(path), errors, repoRoot);
|
|
1608
|
+
return { path, meta, body, projectSkillMatrix };
|
|
1487
1609
|
}
|
|
1488
1610
|
|
|
1489
1611
|
function validateGoalPlan(path, errors) {
|
|
@@ -2478,6 +2600,15 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2478
2600
|
}
|
|
2479
2601
|
}
|
|
2480
2602
|
|
|
2603
|
+
if (childMap) {
|
|
2604
|
+
validateProjectSkillCoverage(
|
|
2605
|
+
childMap.projectSkillMatrix,
|
|
2606
|
+
childTickets.keys(),
|
|
2607
|
+
basename(childMap.path),
|
|
2608
|
+
memberErrors,
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2481
2612
|
for (const [ticketId, artifact] of childTickets) {
|
|
2482
2613
|
for (const dependency of (artifact.meta.blocked_by ?? []).map(String)) {
|
|
2483
2614
|
if (!childTickets.has(dependency)) memberErrors.push(`${ticketId}: blocked_by references missing ${dependency}`);
|
|
@@ -2739,7 +2870,7 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2739
2870
|
const ticketMode = isDirectory(join(change, "ticket"));
|
|
2740
2871
|
const mapRequired = new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode);
|
|
2741
2872
|
const mapPath = join(change, "tickets-map.md");
|
|
2742
|
-
const ticketsMap = isFile(mapPath) || mapRequired ? validateMap(mapPath, errors) : null;
|
|
2873
|
+
const ticketsMap = isFile(mapPath) || mapRequired ? validateMap(mapPath, errors, repoRoot) : null;
|
|
2743
2874
|
const goalPlanPath = join(change, "goal-plan.md");
|
|
2744
2875
|
const goalPlan = isFile(goalPlanPath) || stage === "goal-plan"
|
|
2745
2876
|
? validateGoalPlan(goalPlanPath, errors)
|
|
@@ -2800,6 +2931,15 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2800
2931
|
}
|
|
2801
2932
|
}
|
|
2802
2933
|
|
|
2934
|
+
if (ticketsMap) {
|
|
2935
|
+
validateProjectSkillCoverage(
|
|
2936
|
+
ticketsMap.projectSkillMatrix,
|
|
2937
|
+
tickets.keys(),
|
|
2938
|
+
basename(ticketsMap.path),
|
|
2939
|
+
errors,
|
|
2940
|
+
);
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2803
2943
|
if (stage === "implement" && ticketMode && changeStatus) {
|
|
2804
2944
|
const worktreesByTicket = new Map(
|
|
2805
2945
|
(Array.isArray(changeStatus.worktrees) ? changeStatus.worktrees : [])
|