Skip to content

feat(cli): Add skills#175

Merged
misonijnik merged 54 commits into
mainfrom
misonijnik/skills
Jun 10, 2026
Merged

feat(cli): Add skills#175
misonijnik merged 54 commits into
mainfrom
misonijnik/skills

Conversation

@misonijnik

Copy link
Copy Markdown
Member

No description provided.

@misonijnik misonijnik force-pushed the misonijnik/skills branch 6 times, most recently from a21f81e to 3b8d3be Compare June 8, 2026 22:24
@misonijnik misonijnik marked this pull request as ready for review June 8, 2026 22:37
Saloed and others added 22 commits June 10, 2026 16:22
The bare `opentaint` binary failed to auto-load the bundled analyzer,
autobuilder and rules, falling back to ~/.opentaint/install/lib instead.

GetBundledLibPath/GetBundledJREPath resolved artifacts at <exe-dir>/lib,
which only matches the flat managed layout (~/.opentaint/install/). The
`make install` FHS layout puts the binary in <prefix>/bin and artifacts
in <prefix>/lib (siblings), so the bundled tier never matched. Only the
opentaint-dev wrapper worked, via its explicit --analyzer-jar ../lib
flag.

resolveBundledDir now checks both <exe-dir>/<name> (flat) and
<exe-dir>/../<name> (FHS), preferring whichever exists. Also bundle the
ruleset in `make install` (rules/ruleset -> $(LIBDIR)/rules) so all
three
artifacts ship in the install lib and load relative to the binary.
When scanning with a make-install (FHS) build, the header printed
`Bundled ruleset: rules/v0.2.0` — a nominal version that looked like the
stock release even though the bundled lib holds the user's own modified
rules. Same for the analyzer/autobuilder jars.

ArtifactDisplayVersion is now tier-aware: an artifact resolved from the
bundled tier (the lib next to the binary, which the user controls)
renders
as `custom (<path>)`, since its nominal version may not match its
content.
Managed install/cache releases still display their version string.

Adds resolveArtifactTier (resolveArtifactPath now delegates to it) and a
bundled-tier display test case.
- Add exported utils.PathExists; route health and testutil through it,
  removing duplicate pathExistsCmd/fileExists copies
- Rename health.displayVersion to shortVersion to avoid clashing with
  utils.displayVersion
- Add utils.WriteFiles and use it from testrule/testapprox Scaffold,
  dropping the repeated mkdir+write loops
- Document the shared-global coupling between test rule reachability and
  the scan command
misonijnik and others added 23 commits June 10, 2026 16:22
ArtifactDisplayVersion embeds the jar path in the custom label ("custom
(<path>)"), which health then repeated on its own path node. Split the
display helpers by context: ArtifactVersionWithPath keeps the path
inline
for single-line displays (scan, compile), while the new ArtifactVersion
collapses a custom build to a bare "custom" for displays that show the
path separately (health). The custom-vs-release decision is now a shared
isCustomArtifact predicate.
The colon dangled awkwardly on a parent node whose value hangs on the
line below. Remove it from the shared FieldNode primitive (and health's
label), so scan, compile, and health tree nodes read "Autobuilder"
rather
than "Autobuilder:". The inline Field "key: value" form keeps its colon.
Collapse logic that had been copy-pasted across the scan and test
commands into reusable libs, keeping the command layer thin:

- utils.EnsureRulesPath centralizes the built-in rules
  resolve+download dance (was duplicated in scan, health, test rule run)
- addDataflowApproximations / addPassthroughApproximations share the
  approximation-input loops between scan and test rule run
- utils.CopyFile replaces an ad-hoc file copy in test init
- testutil.ResolveJar moves the 4-tier test-util JAR lookup out of the
  cmd layer into its owning lib

Also tidy up a few supportability issues surfaced in review:

- drop the never-read analyzerError.message field and fold the
  duplicated print+return in classifyAnalyzerError into one path
- gate scan's viper "scan.*" bindings behind a bindViper flag so the
  reachability alias no longer re-binds them and steal config precedence
- normalize a stray 0755 to 0o755

Behavior-preserving except health now reports a rules-resolution error
to stderr instead of swallowing it. Build, vet, and tests pass.
Surface the npm package and install-free npx workflow alongside the
existing Homebrew and install-script methods, and note install-free
npx usage in the usage guide.
A fragment-less join ref names a rule in the referencing file; the analyzer
resolves it to "<currentFile>:<shortId>" and filters loaded rules by exact
full-id match. Passing the bare id through left same-file referenced rules
(three exist in the builtin ruleset) silently dropped from reachability runs.
…classifier

The analyzer exits 0 even when rule-test samples fail; the verdict lives only
in test-result.json, which the CLI never read — so the documented exit-code
contract ('0 = all tests passed') was not enforced. Test runs now parse the
result file, print a pass/fail summary, and exit 2 on failures.

Also: delete cmd/analyzer_exit.go (a verbatim duplicate of internal/analyzer),
share the exit-codes help text and common flag registrations between the two
run commands, skip the builtin-rules download for approximation tests (the
harness rule is self-contained), and surface non-ENOENT project-model stat
errors instead of swallowing them.
… read override from ArtifactDef

Every official install channel (install.sh, brew, npm, release archives) lays
lib/ flat next to the binary and resolves at the bundled tier, so classifying
TierBundled as 'custom' regressed the SARIF tool version and the info trees to
'custom (<local path>)' for all end users. The release pipeline now stamps
lib/release-versions.yaml; a bundled resolution matching the embedded
versions.yaml keeps its pinned version, while make-install dev layouts (no
marker) still read as custom — the original intent of the tier check.

The jarPathOverride parameter on the display helpers duplicated
ArtifactDef.Override at every call site; the helpers now read the def.
… analyzer's real runtime

Single-component mode printed an empty path and exited 0 on resolution
failures, so scripts couldn't detect a missing dependency. The runtime
component reported a system Java (or a stale install-tier JRE) that the
analyzer runner — which pins the managed Adoptium JRE and never tries system
Java — would not use. Both now share the runner's probe via
utils.FindCurrentManagedJRE, and the autobuilder/analyzer cases collapse into
one resolveJarComponent.
…Java runner policies

ensureAnalyzerAvailable/ensureAutobuilderAvailable were near-identical bodies
in different command files, reached into across commands; both now delegate to
one ensureArtifactJar(def) in artifacts.go driven entirely by the ArtifactDef
registry. The analyzer/autobuilder JavaRunner construction chains (copied in
scan, compile, and the test runner) collapse into newAnalyzerJavaRunner /
newAutobuilderJavaRunner.
…flags beat config/env

The scan.* viper keys were bound only to scanCmd's flag instances at init
time, so when 'test rule reachability' parsed explicit --timeout/--max-memory
/--code-flow-limit values, initConfig's viper.Unmarshal silently overwrote
them with config-file or OPENTAINT_SCAN_* env values (the bound scan flags
had Changed=false). Config loading now runs from the root PersistentPreRunE
and rebinds the keys to whichever command is executing.
…nd builtin download

currentScanBuilder promised that new flags propagate to every suggestion but
dropped --rule-id, --severity, both approximation flags, and
--track-external-methods, so the Docker-fallback and --project-model
suggestions ran materially different scans. scanPlan.mode fully derived from
needsCompilation (ScanMode removed); the builtin-ruleset download loop ran
EnsureRulesPath once per builtin entry for nothing.
…ject

The Gradle layout lived in the cobra command file and hardcoded
'libs/opentaint-sast-test-util.jar' in the template while the jar copy used
testutil.JarName three hops away — a coherent rename would scaffold projects
that fail only at sample-compile time. The layout now lives in one package
sharing the JarName constant, writes through utils.WriteFiles/CopyFile (no
redundant MkdirAll loop), and the jar resolves once per command instead of
once per kind. generate_jar.go reuses utils.CopyFile (gaining close-error
checking); package-internal identifiers are unexported.
…pper portability

- bare 'make cli' (and parallel 'make -j all') died on fresh checkouts: go
  generate hard-requires the test-util jar but the cli target had no core
  dependency
- a relative PREFIX split the install: root resolved BINDIR against the repo
  root while cli/Makefile's abspath resolved it against cli/ — BINDIR is now
  passed absolutized
- the opentaint-dev wrapper depended on non-POSIX realpath; it now falls back
  to plain $0 resolution
- 'make install' built the CLI twice (cli prerequisite + go install); cli
  install now reuses the build output, which also makes BINARY_NAME effective
- the core target ran three Gradle invocations; one invocation configures once
  and parallelizes the jar tasks
Released binaries could never resolve opentaint-sast-test-util.jar: the
lib-assembly step shipped only analyzer/autobuilder/rules and goreleaser ran
without go generate, so the go:embed held only the README placeholder and
'opentaint test rule|approximation init' hard-failed for every non-source
install. The jar is now built and embedded before goreleaser (the Docker build
uses the same workspace, so its context picks the generated jar up too) and
bundled into lib/. lib/release-versions.yaml marks the assembled lib dir as an
unmodified official release so bundled-tier installs keep their pinned version
labels.
…l results, and locale encoding

A finding file whose sarif_hashes list was rewritten in YAML block style made
HASHES_RE.sub a silent no-op: merged hashes were never persisted, so every
rescan counted the same hashes as new and clobbered the analyst's verdict back
to pending, forever. Both styles now parse and rewrite, and a missing key is
prepended rather than dropped. Also: 'results': null (aborted runs) no longer
raises TypeError, and all file I/O pins encoding=utf-8.
The 'test rule run' example omitted the test project's test-rules marker
ruleset, so the documented flow reported every positive sample as a false
negative; the reachability example replaced the builtin default (pflag array
semantics), silently breaking ref expansion into builtin lib rules — the
debug-rule skill already showed the correct two-ruleset form. The reachability
help example also lacked the 'java/' ruleset-root prefix real rule ids carry.
…t paths across skills

- the create-pass-through load-check told subagents to run a full scan on the
  main project model, contradicting appsec-agent's no-scan rule for subagents;
  it is now explicitly standalone-only
- appsec-agent's blanket 'approximation over a built-in errors at load' applied
  only to dataflow approximations and contradicted its own passThrough merge
  rule three lines up
- analyze-external-methods' tracking templates omitted the artifact field that
  appsec-agent's single-source-of-truth schema declares
- triage.md's verbatim command mixed a skill-dir-relative script path with
  project-relative arguments; no cwd satisfied both
Remove the comments introduced on this branch (Go, helper scripts,
Makefile, release CI workflow). Preserves go: directives, shebangs,
Python docstrings, and all pre-existing comments.
Use a single VersionMarkerName constant for both the bundled tier (next
to the binary) and the install tier, renaming the bundled marker from
release-versions.yaml to .versions to match the install tier. Add an
explicit lib/.versions entry to the goreleaser archives so the dotfile
is reliably bundled regardless of how lib/** treats hidden files.
The explicit `- src: lib/.versions` archive entries added in b6cb1d7
were redundant and fragile: goreleaser's fileglob already bundles the
dotfile via `lib/**`, while a literal pattern with no wildcard
hard-fails
the entire release when the file is absent ("globbing failed for pattern
lib/.versions"). Remove both entries and guard marker creation directly
in the workflow so a genuinely missing .versions fails loudly at the
point it should be written rather than silently shipping or dying in
goreleaser.
@misonijnik misonijnik merged commit 296bca1 into main Jun 10, 2026
14 checks passed
@misonijnik misonijnik deleted the misonijnik/skills branch June 10, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants