mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Minimal Electron shell over the DSH JSON-RPC runtime — a first-look at what a ChatGPT.app-style host on top of the DeepSeek Harness looks like, with the harness's normally-invisible internals (trace timeline, context surface, subagent tree, compaction, plugin registry, rubrics) brought forward as first-class UI surfaces so plugin authors and researchers can see what the agent is actually doing. Runs against three keyless-to-live profiles (stdio-echo works on master out of the box; daemon-echo / daemon-vibe-echo activate once the daemon-demo lands; stdio-deepseek and daemon-vibe hit the real DeepSeek API when you supply a key). HARNESS_DEV auto-resolves to the in-repo runtime when this shell ships under examples/desktop/, so a fresh clone launches without config; env DSH_DEV_ROOT overrides for custom layouts, and a sibling deepseek-harness-dev/ checkout is the original dev workflow. Cold-clone gate (P0 fixes for first-time-clone usability): - HARNESS_DEV: 3-candidate resolver (env → walk-up in-repo marker → sibling), unit-tested via mock fs so ordering is locked without needing either real layout on disk. - config yml leaves rewritten at assemble time so the sibling-clone paths (../../deepseek-harness-dev/examples/echo-agent/…) become the in-repo paths (../../echo-agent/…) in the released tree — source yml stays usable for local dev, released tree ships a working shape. - pnpm-workspace.yaml allowBuilds.electron = true (was placeholder). - missing-key card in stdio-deepseek offers a one-click switch to stdio-echo (the keyless profile that works on master) rather than daemon-echo (blocked on the not-yet-shipped daemon-demo). - assemble-oss-release.sh rewrites the source-side breadcrumb name 'dsh-desktop-demo' → 'dsh-desktop' for the released package.json. FOUC guard on the onboarding gate (41fc5df carried) keeps the first-launch splash from flashing before the runtime probe finishes. Test suite (1634 tests in source, 3990 in the runtime repo) covers resolver ordering, renderer classifiers, trace timeline shape, compaction diff rendering, rubric parity, and the missing-key onboarding paths.
468 lines
22 KiB
JavaScript
468 lines
22 KiB
JavaScript
#!/usr/bin/env node
|
||
/*
|
||
* comment-sweep-apply.js
|
||
*
|
||
* Mechanical stripper. Reads every .js file under src/main, src/preload,
|
||
* src/renderer and applies the artifact-reference strip recipe from
|
||
* tools/comment-sweep-rules.md.
|
||
*
|
||
* The stripping is conservative: it edits only comment TEXT (the run of
|
||
* characters after `//` or inside `/* … *\/`), not code. If a comment line
|
||
* becomes empty after stripping we delete the line; otherwise we rewrite
|
||
* the comment in-place, preserving the leading `//` or ` * ` / `/*` / `*\/`
|
||
* shape.
|
||
*
|
||
* Usage:
|
||
* node tools/comment-sweep-apply.js --dry-run # print planned edits, don't write
|
||
* node tools/comment-sweep-apply.js --apply # write files in place
|
||
* node tools/comment-sweep-apply.js --scope=main # main+preload only
|
||
* node tools/comment-sweep-apply.js --scope=renderer # renderer only
|
||
*
|
||
* Scope split maps 1:1 onto the two commits (main+preload / renderer) the
|
||
* team-lead brief calls for.
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const SCOPES = {
|
||
main: ['src/main', 'src/preload'],
|
||
renderer: ['src/renderer'],
|
||
all: ['src/main', 'src/preload', 'src/renderer'],
|
||
};
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Strip recipe. Each rule is (pattern, replacement, description). Applied in
|
||
// order; a rule may match zero, one, or multiple times per line. Rules 1-4
|
||
// (LEADING) run only against the START of the comment text.
|
||
// -----------------------------------------------------------------------------
|
||
|
||
// LEADING artifact-prefix rules. Each captures the prefix + trailing
|
||
// delimiter (`:` / `—` / `.` / `,`). Applied only if the match starts at
|
||
// column 0 of the comment text.
|
||
const LEADING_STRIPPERS = [
|
||
// Fresh-eyes P0 (2026-07-18, review-fresh-eyes.md #4 [+ team-lead follow-up]):
|
||
{ name: 'fresh-eyes-p0-full', re: /^\s*Fresh[- ]eyes\s+P0\s*\([^)]*review[- ]fresh[- ]eyes\.md[^)]*\)\s*[::—]?\s*/i },
|
||
// Fresh-eyes P0 (2026-07-18): — dated but no review-md coord
|
||
{ name: 'fresh-eyes-p0-dated', re: /^\s*Fresh[- ]eyes\s+P0\s*\([^)]*\)\s*[::—]\s*/i },
|
||
// Fresh-eyes P0: (bare)
|
||
{ name: 'fresh-eyes-p0-bare', re: /^\s*Fresh[- ]eyes\s+P0\s*[::—]\s*/i },
|
||
// Ticket #NNN [X] [(2026-07-17)] [phase 2 (pi §2.3)]: prefix (matches ticket-letter and ticket-num)
|
||
{ name: 'ticket-num-prefix', re: /^\s*Ticket\s+#?\d+(?:\s+[A-Z])?(?:\s*\([^)]*\))?(?:\s+step\s+\d+|\s+phase\s+\d+(?:\s*\([^)]*\))?)?\s*[::—]\s*/i },
|
||
{ name: 'ticket-letter-prefix', re: /^\s*Ticket\s+[A-Z](?:\s*\([^)]*\))?\s*[::—]\s*/ },
|
||
// task #NNN [/ trace-viz §X.Y] [(2026-07-17…)] [rec 22-bis[…]] [phase 2 (pi §2.3)]:
|
||
//
|
||
// The `/ …` continuation matches compound refs like
|
||
// task #201 / trace-viz §4d:
|
||
// task #158 / density-spec §3:
|
||
// where the reader wanted "here is what the constraint is called in two
|
||
// artifact systems". We drop the whole pileup.
|
||
//
|
||
// Widened 2026-07-18: also accept a small trailing tag word or two before
|
||
// the delimiter, and `.` as an end-of-prefix delim in addition to `:`/`:`/`—`.
|
||
// Handles the pi-style `Task #49 lane:`, `Task #225 selfie seam:`,
|
||
// `Task #103 P0-4 (2026-07-16).` shapes.
|
||
{ name: 'task-num-prefix', re: /^\s*(?:Task|task)\s*#\d+(?:\s*\/\s*[\w §.a-z-]+)?(?:\s+[\w-]+(?:\s+[\w-]+)?)?(?:\s*\([^)]*\))?(?:\s+rec\s+[\d-]+(?:-bis)?)?(?:\s+phase\s+\d+(?:\s*\([^)]*\))?)?\s*[::—.]\s*/ },
|
||
// F-N (2026-07-18 e2e audit): prefix
|
||
{ name: 'finding-letter-prefix', re: /^\s*F-\d+\s*\([^)]*(?:audit|e2e)[^)]*\)\s*[::—]\s*/i },
|
||
// team-lead §X.Y [ruling|正面参照|dispatch verbatim] [(...)]:
|
||
{ name: 'team-lead-prefix', re: /^\s*[Tt]eam[- ]lead\s+§[\d.]+(?:\s+\w+)?(?:\s*\([^)]*\))?\s*[::—]\s*/ },
|
||
// rec 22-bis [(2026-07-17|pi §X.Y)]:
|
||
{ name: 'rec-num-prefix', re: /^\s*rec\s+\d+(?:-bis)?(?:\s+phase\s+\d+)?(?:\s*\([^)]*\))?\s*[::—]\s*/i },
|
||
// Round-visual N1 (2026-07-16):
|
||
{ name: 'round-visual-prefix', re: /^\s*Round[- ]visual\s+[A-Z]?\d+(?:\s*\([^)]*\))?\s*[::—]\s*/i },
|
||
// Clickability audit D3 [fix] (2026-07-17):
|
||
{ name: 'audit-batch-prefix', re: /^\s*(?:Clickability\s+audit|hygiene\s+batch|trace[- ]parity\s+batch)\s+[\w\d]+(?:\s+\w+)?(?:\s*\([^)]*\))?\s*[::—]\s*/i },
|
||
// 2026-07-XX round-N shot NN: (dated e2e/round prefix)
|
||
{ name: 'dated-round-prefix', re: /^\s*20\d{2}-\d{2}-\d{2}\s+(?:round|QA|e2e|老板|walkthrough|trace[- ]parity)[^::]*[::—]\s*/ },
|
||
// 2026-07-XX delta: / 2026-07-XX addendum (...): — generic process
|
||
// banner starting with a date + one-word tag.
|
||
{ name: 'dated-tag-prefix', re: /^\s*20\d{2}-\d{2}-\d{2}\s+\w+(?:\s*\([^)]*\))?\s*[::—]\s*/ },
|
||
// Clickability audit fills (...2026-07-17): — audit-batch-prefix widened
|
||
// to accept the trailing "fills"/word before the paren+delim.
|
||
{ name: 'audit-batch-fills-prefix', re: /^\s*Clickability\s+audit\s+\w+(?:\s*\([^)]*\))?\s*[::—]?\s*/i },
|
||
// Density-spec L0 budget: — density-spec followed by L\d level ref
|
||
// instead of §N (LangSmith-style level naming).
|
||
{ name: 'density-spec-level-prefix', re: /^\s*[Dd]ensity[- ]spec\s+L\d[^::]*[::]\s*/ },
|
||
];
|
||
|
||
// TRAILING (parenthetical) rules. Applied to the whole comment text; may
|
||
// match at any position. Non-greedy.
|
||
const TRAILING_STRIPPERS = [
|
||
// (Fresh-eyes P0 (2026-07-18, review-fresh-eyes.md #N)) — nested variant
|
||
{ name: 'paren-fresh-eyes', re: /\s*\(\s*Fresh[- ]eyes\s+P0[^()]*review[- ]fresh[- ]eyes\.md[^()]*\)/gi },
|
||
// (review-fresh-eyes.md #N) / (review-*.md #N)
|
||
{ name: 'paren-review-md', re: /\s*\(review-[a-z][\w-]*\.md\s*#\d+\)/gi },
|
||
// (QA round-N shot NN)
|
||
{ name: 'paren-qa-round', re: /\s*\(QA\s+round-\d+(?:\s+shot\s+\d+)?\)/gi },
|
||
// (round-N) / (round-N shot NN)
|
||
{ name: 'paren-round', re: /\s*\(round-\d+(?:\s+shot\s+\d+)?\)/gi },
|
||
// (2026-07-17|老板实测|team-lead §X.Y|正面参照) — mixed process paren
|
||
{ name: 'paren-dated-process', re: /\s*\((?:20\d{2}-\d{2}-\d{2})[^()]*(?:老板|team[- ]lead|正面|指令|walkthrough|e2e[- ]audit|round-\d)[^()]*\)/gi },
|
||
// (Ticket #N) / (task #N) as trailing tag, incl. trailing tokens like
|
||
// (task #103 P0-4, 2026-07-16)
|
||
// (task #37 layer 1)
|
||
// (2026-07-17, task #49) — leading-date variant
|
||
{ name: 'paren-ticket-num', re: /\s*\((?:Ticket|task)\s*#?\d+(?:[,\s][^()]*)?\)/gi },
|
||
{ name: 'paren-dated-task', re: /\s*\(20\d{2}-\d{2}-\d{2}\s*,\s*(?:Ticket|task)\s*#?\d+(?:[,\s][^()]*)?\)/gi },
|
||
// 205-Δ3 / 205-Δ4 boss-delta bare tag with trailing punctuation
|
||
{ name: 'inline-boss-delta', re: /\s*\b205-Δ\d+\b/g },
|
||
// (F-1 …)
|
||
{ name: 'paren-finding', re: /\s*\(F-\d+(?:\s*[,;]\s*\d{4}-\d{2}-\d{2}[^)]*)?\)/g },
|
||
// packages/x/y/z.ts:33-52 → strip. If the coord sat inside its own paren
|
||
// `(packages/foo.ts:33-52)`, drop the paren too so we don't leave empty
|
||
// "wire () carries..." debris.
|
||
{ name: 'upstream-path-paren', re: /\s*\(packages\/[\w-]+\/[\w-]+\/src\/[\w./-]+\.ts:\d+(?:-\d+)?\)/g },
|
||
{ name: 'upstream-path', re: /\s*\bpackages\/[\w-]+\/[\w-]+\/src\/[\w./-]+\.ts:\d+(?:-\d+)?/g },
|
||
// Compound "QA round-N shot NN" as a bare inline tag (must run BEFORE
|
||
// inline-round so we don't leave "QA shot NN" debris).
|
||
{ name: 'inline-qa-round-shot', re: /\s*\bQA\s+round-\d+\s+shot\s+\d+\b/gi },
|
||
// rec 22-bis / rec 22 as a mid-sentence tag (bare word)
|
||
{ name: 'inline-rec', re: /\s*\brec\s+\d+(?:-bis)?/g },
|
||
// Round-N tag mid-sentence
|
||
{ name: 'inline-round', re: /\s*\bround-\d+\b/gi },
|
||
// Orphan closing `)` LEFT AS THE ENTIRE line body after an upstream-path
|
||
// strip (source line was `path.ts:295-296):` — the strip removed the path
|
||
// but not the parenthesis because it opened on a prior line). Only fires
|
||
// when the body reduces to `)` or `):` with no other content.
|
||
{ name: 'orphan-close-paren', re: /^\s*\)\s*[::]?\s*$/ },
|
||
// Mid-sentence `Ticket #NNN [A-Z] [(2026-…)]` reference — a leftover after
|
||
// the leading-prefix strippers didn't fire because the artifact ref sat
|
||
// inside a longer sentence like:
|
||
// `— Ticket #140 explicitly says "推倒重来"`
|
||
// `Ticket #140. Data source is the growth-v2 IPC`
|
||
// `raw-inject.js — Ticket #15 B (2026-07-17) envelope:'raw' classifier.`
|
||
// Distinct from paren-ticket-num because the artifact ref is bare (no
|
||
// enclosing parens). Bare `Ticket #NNN` is an unambiguous internal artifact
|
||
// reference (unlike bare `#NNN` which per team-lead (B) 2026-07-18 stays as
|
||
// an OSS-verifiable official-repo PR anchor).
|
||
{ name: 'inline-ticket-ref', re: /\s*\bTicket\s+#\d+(?:\s+[A-Z])?(?:\s*\([^)]{0,50}\))?(?:\s+(?:step|phase)\s+\d+)?/g },
|
||
// Mid-sentence `team-lead §X.Y [tag]` reference.
|
||
{ name: 'inline-team-lead-ref', re: /\s*\b[Tt]eam[- ]lead\s+§[\d.]+(?:\s+\w+)?/g },
|
||
// Mid-sentence `task #NNN` — the leading-prefix stripper needs a `:`
|
||
// delimiter; sentences like `strategy list, task #136` or
|
||
// `mergeRecentSessions (task #69 — foo)` leak through.
|
||
{ name: 'inline-task-num-ref', re: /\s*[,;]?\s*\btask\s+#\d+\b/g },
|
||
];
|
||
|
||
// INTERNAL doc references. The OSS release pipeline strips
|
||
// docs/design-refs/ from the shipped artefact, so a reader who follows a
|
||
// `density-spec §N` or `style-guide` reference hits a 404 in the published
|
||
// tree. Team-lead directive (2026-07-18): dangling references are worse than
|
||
// missing ones — strip both. INTERNAL_DOC_KEEP is intentionally empty; the
|
||
// STRIP list covers every internal-only doc pointer we know about.
|
||
const INTERNAL_DOC_KEEP = [];
|
||
const INTERNAL_DOC_STRIP = [
|
||
{ re: /\s*\bpi[- ]agent[- ]ui[- ]study(?:\.md)?\s*§[\d.]+/gi, name: 'pi-study' },
|
||
{ re: /\s*\bLangSmith\s+study\s+§[\d.]+(?:\s+rec\s+\d+)?/gi, name: 'langsmith-study' },
|
||
{ re: /\s*\btrace[- ]parity(?:\s+batch)?(?:\s+task\s+\d+)?/gi, name: 'trace-parity-batch' },
|
||
{ re: /\s*\btrace[- ]viz\s+§[\d.a-z]+/gi, name: 'trace-viz-ref' },
|
||
// density-layering spec references — repo-internal doc, stripped from OSS
|
||
// artefact so references would 404. Matches `density-spec §N`, `density spec §N.M`.
|
||
{ re: /\s*\bdensity[- ]spec\s*§[\d.a-z]+(?:\s*·\s*[\w-]+)?/gi, name: 'density-spec' },
|
||
{ re: /\s*\bdensity[- ]layering[- ]spec\s*§[\d.a-z]+/gi, name: 'density-layering-spec' },
|
||
// style-guide references — same reason.
|
||
{ re: /\s*\bstyle[- ]guide\s*(?:§[\d.a-z]+)?/gi, name: 'style-guide' },
|
||
];
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// Comment-aware line rewriter.
|
||
//
|
||
// A file is edited by walking its comment spans (produced identically to
|
||
// comment-sweep-scan.js) and rewriting each span's text. Two shapes:
|
||
// - // line comment → text = everything after //
|
||
// - /* … */ block → each internal line is treated independently; the
|
||
// leading ` * ` (or ` `) prefix is preserved.
|
||
//
|
||
// After rewriting, the file is emitted with:
|
||
// - Empty // lines → line removed entirely (including trailing \n).
|
||
// - Empty ` * ` lines in a block comment → line removed if the block still
|
||
// has non-empty lines around it; if the whole block becomes empty, the
|
||
// entire /* */ span is removed.
|
||
// -----------------------------------------------------------------------------
|
||
|
||
function rewriteCommentText(text) {
|
||
const originalLeadingWs = (text.match(/^\s*/) || [''])[0];
|
||
let out = text;
|
||
// Sentinel-guard PRE-EXISTING empty parens `()` so the paren-cleanup below
|
||
// (added to collapse `(density-spec §3)` → gone after INTERNAL_DOC_STRIP
|
||
// consumes the content) doesn't clobber legitimate function-call references
|
||
// inside comments, e.g. `daemon.ensureUp()` / `refreshSessionList()`.
|
||
// Any `()` that survives all strips into the output is one we created; the
|
||
// sentinel-restored ones were there in the source and stay.
|
||
const EMPTY_PAREN_MARK = ' |